Compare commits

...

51 Commits

Author SHA1 Message Date
Alexander Rose
7ccd4a1e0d 2.4.1 2021-11-28 13:31:12 -08:00
Alexander Rose
eb41882c56 Merge branch 'master' of https://github.com/molstar/molstar 2021-11-28 13:25:42 -08:00
Alexander Rose
734851a810 changelog 2021-11-28 12:46:12 -08:00
dsehnal
d8498feaef updade npmignore 2021-11-28 14:36:54 +01:00
Alexander Rose
95654175fe improve 'rounded' tube geometry
- correct normals
- circle offset
2021-11-26 15:34:16 -08:00
Alexander Rose
de96244706 allow atoms in aromatic rings to do hydrogen bonds 2021-11-26 14:37:50 -08:00
Alexander Rose
73ac445a44 2.4.0 2021-11-25 14:46:10 -08:00
Alexander Rose
1a1d1d9d30 changelog 2021-11-25 14:41:19 -08:00
Alexander Rose
062aff76da update schemas 2021-11-25 14:40:35 -08:00
Alexander Rose
7d0d24b66d update packages 2021-11-25 14:40:26 -08:00
Alexander Rose
6655672d11 Merge pull request #290 from molstar/smoothing2
Smooth Overpaint and Transparency
2021-11-25 14:13:10 -08:00
Alexander Rose
6e573ae410 reduce args counts in geo exporters 2021-11-25 14:04:58 -08:00
Alexander Rose
1c48c02473 basic overpaint for direct-volume isosurface 2021-11-24 19:49:42 -08:00
Alexander Rose
78be3320ce geo export support smoothed overpaint/transparency 2021-11-24 19:49:05 -08:00
Alexander Rose
c8018800cc grid-based smoothing of Overpaint and Transparency 2021-11-24 19:47:07 -08:00
Alexander Rose
bb795aca98 refactor grid-based color smoothing
- support rgba and alpha values
- CPU and GPU versions
- for Mesh and TextureMesh
2021-11-24 19:43:15 -08:00
Alexander Rose
2cb1279f4c gl compute utils improvements
- CopyRenderable
- readTexture and readAlphaTexture helpers
2021-11-24 19:10:15 -08:00
Alexander Rose
b876c6f618 avoid unnecessary representation state updates 2021-11-24 18:51:16 -08:00
Alexander Rose
3a7dfc055e add Representation.geometryVersion
- increments whenever the geometry of any visual changes
2021-11-24 18:49:10 -08:00
Alexander Rose
928e521ac7 improve handling of .meta in Mesh & TextureMesh 2021-11-24 18:36:04 -08:00
Alexander Rose
e5e9598e4b changelog 2021-11-24 18:31:08 -08:00
Alexander Rose
e6e1809592 Fix secondary-structure property handling
- StructureElement.Property was incorrectly resolving type & key
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
2021-11-24 18:30:53 -08:00
Alexander Rose
812f97ddb7 skip picking/depth pass for volume rendering
- not supported in shader anyway
- was printing 'no output' warning in Chrome console
2021-11-24 18:20:42 -08:00
Alexander Rose
c6b814b31b re-enable VAO with better workaround 2021-11-24 18:17:48 -08:00
Alexander Rose
98566fa389 improve error handling
- console.error if not re-thrown
- better messages for users
2021-11-24 18:15:47 -08:00
David Sehnal
4318c89bdb Merge pull request #288 from jpattle/allow-v3-sdf
Added the ability to handle v3000 sd files
2021-11-23 15:49:42 +01:00
Jason Pattle
b41a97ce6a Added a separate function to handle v2 counts and refactored the existing code that distinguishes v2 and v3 atom and bond counts 2021-11-23 10:30:09 +00:00
Jason Pattle
862c384dc0 Added the ability to handle v3000 sd files. Added a set of utility functions for parsing atoms and bonds from v3000 sd files. Updated the existing sdf parser to determine the version and run the v3000 sd file parser functions instead of the default v2000 ones. Added tests to verify parsing functionality for example v3000 ctab 2021-11-22 16:00:35 +00:00
Alexander Rose
26cc7e94c2 2.3.9 2021-11-20 16:58:28 -08:00
Alexander Rose
c6fe6ddcba switch off VAO support for now 2021-11-20 16:54:38 -08:00
Alexander Rose
154984e74d 2.3.8 2021-11-20 16:30:22 -08:00
Alexander Rose
72fcaf8321 changelog 2021-11-20 16:24:54 -08:00
Alexander Rose
0c14ca5888 workaround for VAO issue in Chrome 96 2021-11-20 16:14:30 -08:00
Alexander Rose
a85ede5058 fix unused vertex attribute handling 2021-11-20 16:13:18 -08:00
Alexander Rose
db49a16184 fix double canvas context creation 2021-11-20 12:52:47 -08:00
dsehnal
0704db2343 replace webpack-version-file-plugin 2021-11-20 13:45:28 +01:00
dsehnal
425dca4665 fix sass division 2021-11-20 12:55:46 +01:00
dsehnal
8d65ccabd2 update packages
- use sass instead of node sass
2021-11-20 12:49:26 +01:00
Alexander Rose
cbd417ca13 2.3.7 2021-11-15 19:31:07 -08:00
Alexander Rose
1578211157 add helix profile option to cartoon repr 2021-11-13 13:16:11 -08:00
Alexander Rose
15932dc5df handle parent in Structure.remapModel 2021-11-13 09:23:57 -08:00
dsehnal
7db2205956 changelog 2021-11-09 21:18:55 +01:00
dsehnal
d87f0d236a ViewerOptions.collapseRightPanel 2021-11-09 21:17:01 +01:00
dsehnal
16daca6008 undo test code 2021-11-09 21:13:28 +01:00
dsehnal
a0d919c8db Viewer.loadTrajectory 2021-11-09 21:12:24 +01:00
David Sehnal
ffee2bf1c4 Merge pull request #285 from MadCatX/tweak-measurement-order-labels
Tweak measurement order labels
2021-11-09 10:28:13 +01:00
Michal Malý
de77f6ac59 Fix a visual glitch where the label border was initially rendered with
half of the intended size.
2021-11-09 09:59:25 +01:00
Michal Malý
c8c2ebcd65 Reduce Z-offset of measurement order labels to avoid too early clipping 2021-11-09 09:59:25 +01:00
Michal Malý
42796b984f Reduce border width of measurement order labels 2021-11-09 09:58:58 +01:00
David Sehnal
746557bf52 Merge pull request #284 from russellp17/master
Move jest types to devDependencies
2021-11-08 22:48:15 +01:00
Russell Parker
a5020a9e96 Move jest types to devDependencies 2021-11-08 15:58:14 -05:00
87 changed files with 2893 additions and 3133 deletions

View File

@@ -1 +1 @@
tsconfig.commonjs.tsbuildinfo
lib/tsconfig.commonjs.tsbuildinfo

View File

@@ -6,6 +6,36 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v2.4.1] - 2021-11-28
- Fix: allow atoms in aromatic rings to do hydrogen bonds
## [v2.4.0] - 2021-11-25
- Fix secondary-structure property handling
- StructureElement.Property was incorrectly resolving type & key
- 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
- Workaround: switch off VAO support for now
## [v2.3.8] - 2021-11-20
- Fix double canvas context creation (in plugin context)
- Fix unused vertex attribute handling (track which are used, disable the rest)
- Workaround for VAO issue in Chrome 96 (can cause WebGL to crash on geometry updates)
## [v2.3.7] - 2021-11-15
- Added ``ViewerOptions.collapseRightPanel``
- Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
- Fix: handle parent in Structure.remapModel
- Add ``rounded`` and ``square`` helix profile options to Cartoon representation (in addition to the default ``elliptical``)
## [v2.3.6] - 2021-11-8
- Add additional measurement controls: orientation (box, axes, ellipsoid) & plane (best fit)

3722
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.3.6",
"version": "2.4.1",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -89,52 +89,51 @@
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.1.0",
"@graphql-codegen/cli": "^2.2.0",
"@graphql-codegen/cli": "^2.3.0",
"@graphql-codegen/time": "^3.1.0",
"@graphql-codegen/typescript": "^2.2.2",
"@graphql-codegen/typescript": "^2.4.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
"@graphql-codegen/typescript-graphql-request": "^4.1.4",
"@graphql-codegen/typescript-operations": "^2.1.6",
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
"@graphql-codegen/typescript-operations": "^2.2.1",
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.32.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"benchmark": "^2.1.4",
"concurrently": "^6.3.0",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.3.0",
"eslint": "^7.32.0",
"css-loader": "^6.5.1",
"eslint": "^8.3.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.0",
"graphql": "^15.6.0",
"http-server": "^13.0.2",
"jest": "^27.2.4",
"mini-css-extract-plugin": "^2.3.0",
"node-sass": "^6.0.1",
"graphql": "^15.7.2",
"http-server": "^14.0.0",
"jest": "^27.3.1",
"mini-css-extract-plugin": "^2.4.5",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass-loader": "^12.1.0",
"simple-git": "^2.46.0",
"sass": "^1.43.5",
"sass-loader": "^12.3.0",
"simple-git": "^2.47.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.0",
"ts-jest": "^27.0.5",
"typescript": "^4.4.3",
"webpack": "^5.56.0",
"webpack-cli": "^4.8.0",
"webpack-version-file-plugin": "^0.4.0"
"style-loader": "^3.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.5.2",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.1",
"@types/compression": "1.7.2",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.2",
"@types/node": "^16.10.2",
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.27",
"@types/react-dom": "^17.0.9",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
"body-parser": "^1.19.0",
@@ -142,13 +141,13 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.6",
"immer": "^9.0.7",
"immutable": "^3.8.2",
"node-fetch": "^2.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rxjs": "^7.3.1",
"swagger-ui-dist": "^3.52.3",
"rxjs": "^7.4.0",
"swagger-ui-dist": "^4.1.1",
"tslib": "^2.3.1",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"

View File

@@ -36,7 +36,7 @@
emdbProvider: 'rcsb',
});
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
</script>

View File

@@ -9,25 +9,28 @@ import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Mp4Export } from '../../extensions/mp4-export';
import { GeometryExport } from '../../extensions/geo-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectSelector } from '../../mol-state';
@@ -71,6 +74,7 @@ const DefaultViewerOptions = {
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
@@ -114,7 +118,7 @@ export class Viewer {
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
@@ -328,6 +332,56 @@ export class Viewer {
});
}
/**
* @example
* viewer.loadTrajectory({
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
* preset: 'all-models' // or 'default'
* });
*/
async loadTrajectory(params: LoadTrajectoryParams) {
const plugin = this.plugin;
let model: StateObjectSelector, coords: StateObjectSelector;
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
const data = params.model.kind === 'model-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
model = await plugin.builders.structure.createModel(trajectory);
} else {
const data = params.model.kind === 'topology-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const provider = plugin.dataFormats.get(params.model.format);
model = await provider!.parse(plugin, data);
}
{
const data = params.coordinates.kind === 'coordinates-data'
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
const provider = plugin.dataFormats.get(params.coordinates.format);
coords = await provider!.parse(plugin, data);
}
const trajectory = await plugin.build().toRoot()
.apply(TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coords.ref
}, { dependsOn: [model.ref, coords.ref] })
.commit();
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
return { model, coords, preset };
}
handleResize() {
this.plugin.layout.events.updated.next(void 0);
}
@@ -343,4 +397,16 @@ export interface VolumeIsovalueInfo {
color: Color,
alpha?: number,
volumeIndex?: number
}
export interface LoadTrajectoryParams {
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
modelLabel?: string,
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
coordinatesLabel?: string,
preset?: keyof PresetTrajectoryHierarchy
}

View File

@@ -84,6 +84,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'DateTime':
case 'Tag':
case 'Implied':
case 'Word':
return wrapContainer('str', ',', description, container);
case 'Real':
return wrapContainer('float', ',', description, container);

View File

@@ -77,7 +77,7 @@ export class GeometryControls extends PluginComponent {
filename: filename + '.' + renderObjectExporter.fileExtension
};
} catch (e) {
this.plugin.log.error('' + e);
this.plugin.log.error('Error during geometry export');
throw e;
}
});

View File

@@ -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];

View File

@@ -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,6 +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 { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
const GeoExportName = 'geo-export';
@@ -48,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;
@@ -90,26 +100,43 @@ 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;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[GeoExportName];
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, outputStride: 3 });
return interpolated.array;
}
const [width, height] = colorTexDim;
const colorGrid = new Uint8Array(width * height * 4);
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;
framebuffer.bind();
values.tColorGrid.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, colorGrid);
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 });
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
return interpolated.array;
}
@@ -194,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;
@@ -236,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) {

View File

@@ -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);
const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
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;
}
// 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);

View File

@@ -79,10 +79,10 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
this.setState({ busy: false });
download(data.blob, data.filename);
} catch {
} catch (e) {
console.error(e);
} finally {
this.setState({ busy: false });
}
}
@@ -91,7 +91,6 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
this.setState({ busy: false });
const a = document.createElement('a');
a.rel = 'ar';
a.href = URL.createObjectURL(data.blob);
@@ -100,7 +99,9 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
a.appendChild(document.createElement('img'));
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
} catch {
} catch (e) {
console.error(e);
} finally {
this.setState({ busy: false });
}
}

View File

@@ -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);

View File

@@ -73,7 +73,7 @@ export class Mp4Controls extends PluginComponent {
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
} catch (e) {
this.plugin.log.error('' + e);
this.plugin.log.error('Error during animation export');
throw e;
}
});

View File

@@ -115,7 +115,8 @@ export class Mp4EncoderUI extends CollapsableControls<{}, State> {
this.setState({ busy: true });
const data = await this.controls.render();
this.setState({ busy: false, data });
} catch {
} catch (e) {
console.error(e);
this.setState({ busy: false });
}
}

View File

@@ -1,9 +1,10 @@
/* eslint-disable */
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 2021-09-12T13:31:40-07:00
// Generated on 2021-11-25T14:34:23-08:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -6769,7 +6770,7 @@ export type Query = {
/** Query root */
export type QueryAssembliesArgs = {
assembly_ids: ReadonlyArray<Maybe<Scalars['String']>>;
assembly_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6802,7 +6803,7 @@ export type QueryBranched_Entity_InstanceArgs = {
/** Query root */
export type QueryBranched_Entity_InstancesArgs = {
instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6814,7 +6815,7 @@ export type QueryChem_CompArgs = {
/** Query root */
export type QueryChem_CompsArgs = {
comp_ids: ReadonlyArray<Maybe<Scalars['String']>>;
comp_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6852,7 +6853,7 @@ export type QueryNonpolymer_Entity_InstanceArgs = {
/** Query root */
export type QueryNonpolymer_Entity_InstancesArgs = {
instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6878,7 +6879,7 @@ export type QueryPolymer_Entity_InstanceArgs = {
/** Query root */
export type QueryPolymer_Entity_InstancesArgs = {
instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -13144,4 +13145,4 @@ export type AssemblySymmetryQueryVariables = Exact<{
}>;
export type AssemblySymmetryQuery = { readonly assembly?: Maybe<{ readonly rcsb_struct_symmetry?: Maybe<ReadonlyArray<Maybe<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<Maybe<string>>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<Maybe<{ readonly avg_rmsd?: Maybe<number>, readonly members: ReadonlyArray<Maybe<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: Maybe<ReadonlyArray<Maybe<string>>> }>> }>>, readonly rotation_axes?: Maybe<ReadonlyArray<Maybe<{ readonly order?: Maybe<number>, readonly start: ReadonlyArray<Maybe<number>>, readonly end: ReadonlyArray<Maybe<number>> }>>> }>>> }> };
export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null | undefined>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null | undefined, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null | undefined> | null | undefined } | null | undefined> } | null | undefined>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null | undefined, readonly start: ReadonlyArray<number | null | undefined>, readonly end: ReadonlyArray<number | null | undefined> } | null | undefined> | null | undefined } | null | undefined> | null | undefined } | null | undefined };

View File

@@ -22,11 +22,11 @@ import { Helper } from '../helper/helper';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { depthMerge_frag } from '../../mol-gl/shader/depth-merge.frag';
import { copy_frag } from '../../mol-gl/shader/copy.frag';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
import { MarkingPass, MarkingProps } from './marking';
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
const DepthMergeSchema = {
...QuadSchema,
@@ -53,27 +53,6 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
return createComputeRenderable(renderItem, values);
}
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
function getCopyRenderable(ctx: WebGLContext, colorTexture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export class DrawPass {
private readonly drawTarget: RenderTarget
@@ -128,8 +107,8 @@ export class DrawPass {
this.postprocessing = new PostprocessingPass(webgl, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.copyFboTarget = getCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = getCopyRenderable(webgl, this.postprocessing.target.texture);
this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = createCopyRenderable(webgl, this.postprocessing.target.texture);
}
reset() {

View File

@@ -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.' }),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 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>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -40,7 +40,7 @@ const v3set = Vec3.set;
const caAdd3 = ChunkedArray.add3;
const caAdd = ChunkedArray.add;
function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number) {
function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number, flip: boolean) {
const { vertices, normals, indices } = state;
const vertexCount = vertices.elementCount;
@@ -74,11 +74,19 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi
v3copy(verticalVector, verticalLeftVector);
}
for (let i = 0; i < 4; ++i) {
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
if (flip) {
for (let i = 0; i < 4; ++i) {
caAdd3(normals, -normalVector[0], -normalVector[1], -normalVector[2]);
}
caAdd3(indices, vertexCount, vertexCount + 1, vertexCount + 2);
caAdd3(indices, vertexCount + 2, vertexCount + 3, vertexCount);
} else {
for (let i = 0; i < 4; ++i) {
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
}
caAdd3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
caAdd3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
}
caAdd3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
caAdd3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
}
/** set arrowHeight = 0 for no arrow */
@@ -193,19 +201,18 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
const width = widthValues[0];
const height = heightValues[0];
const h = arrowHeight === 0 ? height : arrowHeight;
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h, false);
} else if (arrowHeight > 0) {
const width = widthValues[0];
const height = heightValues[0];
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height, false);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height, false);
}
if (endCap && arrowHeight === 0) {
const width = widthValues[linearSegments];
const height = heightValues[linearSegments];
// use negative height to flip the direction the cap's triangles are facing
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, -height, -height);
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height, true);
}
const addedVertexCount = (linearSegments + 1) * 8 +

View File

@@ -1,12 +1,12 @@
/**
* Copyright (c) 2018-2020 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>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ChunkedArray } from '../../../../mol-data/util';
import { cantorPairing, ChunkedArray } from '../../../../mol-data/util';
import { MeshBuilder } from '../mesh-builder';
const normalVector = Vec3();
@@ -30,32 +30,38 @@ function add3AndScale2(out: Vec3, a: Vec3, b: Vec3, c: Vec3, sa: number, sb: num
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3normalize = Vec3.normalize;
const v3negate = Vec3.negate;
const v3copy = Vec3.copy;
const v3scaleAndAdd = Vec3.scaleAndAdd;
const v3cross = Vec3.cross;
const v3dot = Vec3.dot;
const v3unitX = Vec3.unitX;
const caAdd3 = ChunkedArray.add3;
const CosSinCache = new Map<number, { cos: number[], sin: number[] }>();
function getCosSin(radialSegments: number) {
if (!CosSinCache.has(radialSegments)) {
function getCosSin(radialSegments: number, shift: boolean) {
const offset = shift ? 1 : 0;
const hash = cantorPairing(radialSegments, offset);
if (!CosSinCache.has(hash)) {
const cos: number[] = [];
const sin: number[] = [];
for (let j = 0; j < radialSegments; ++j) {
const t = 2 * Math.PI * j / radialSegments;
const t = (j * 2 + offset) / radialSegments * Math.PI;
cos[j] = Math.cos(t);
sin[j] = Math.sin(t);
}
CosSinCache.set(radialSegments, { cos, sin });
CosSinCache.set(hash, { cos, sin });
}
return CosSinCache.get(radialSegments)!;
return CosSinCache.get(hash)!;
}
export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean) {
export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean, crossSection: 'elliptical' | 'rounded') {
const { currentGroup, vertices, normals, indices, groups } = state;
let vertexCount = vertices.elementCount;
const { cos, sin } = getCosSin(radialSegments);
const { cos, sin } = getCosSin(radialSegments, crossSection === 'rounded');
const q1 = Math.round(radialSegments / 4);
const q3 = q1 * 3;
for (let i = 0; i <= linearSegments; ++i) {
const i3 = i * 3;
@@ -65,14 +71,24 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
const width = widthValues[i];
const height = heightValues[i];
const rounded = crossSection === 'rounded' && height > width;
for (let j = 0; j < radialSegments; ++j) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]);
if (radialSegments === 2) {
v3copy(normalVector, v);
v3normalize(normalVector, normalVector);
if (j !== 0 || i % 2 === 0) v3negate(normalVector, normalVector);
if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[j], width * sin[j]);
const h = v3dot(v, v3unitX) < 0
? (j < q1 || j >= q3) ? height - width : -height + width
: (j >= q1 && j < q3) ? -height + width : height - width;
v3scaleAndAdd(surfacePoint, surfacePoint, u, h);
if (j === q1 || j === q1 - 1) {
add2AndScale2(normalVector, u, v, 0, 1);
} else if (j === q3 || j === q3 - 1) {
add2AndScale2(normalVector, u, v, 0, -1);
} else {
add2AndScale2(normalVector, u, v, cos[j], sin[j]);
}
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]);
add2AndScale2(normalVector, u, v, width * cos[j], height * sin[j]);
}
v3normalize(normalVector, normalVector);
@@ -82,19 +98,37 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
}
}
const radialSegmentsHalf = Math.round(radialSegments / 2);
for (let i = 0; i < linearSegments; ++i) {
for (let j = 0; j < radialSegments; ++j) {
// the triangles are arranged such that opposing triangles of the sheet align
// which prevents triangle intersection within tight curves
for (let j = 0; j < radialSegmentsHalf; ++j) {
caAdd3(
indices,
vertexCount + i * radialSegments + (j + 1) % radialSegments,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
vertexCount + i * radialSegments + j
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + i * radialSegments + j // b
);
caAdd3(
indices,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
vertexCount + (i + 1) * radialSegments + j,
vertexCount + i * radialSegments + j
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j // b
);
}
for (let j = radialSegmentsHalf; j < radialSegments; ++j) {
caAdd3(
indices,
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j // b
);
caAdd3(
indices,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
);
}
}
@@ -111,11 +145,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
const width = widthValues[0];
const height = heightValues[0];
let height = heightValues[0];
const rounded = crossSection === 'rounded' && height > width;
if (rounded) height -= width;
vertexCount = vertices.elementCount;
for (let i = 0; i < radialSegments; ++i) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]);
v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height);
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
}
caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
@@ -141,11 +182,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
const width = widthValues[linearSegments];
const height = heightValues[linearSegments];
let height = heightValues[linearSegments];
const rounded = crossSection === 'rounded' && height > width;
if (rounded) height -= width;
vertexCount = vertices.elementCount;
for (let i = 0; i < radialSegments; ++i) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]);
v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height);
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
}
caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);

View File

@@ -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 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);
const o = (i * vertexCount + j) * outputStride;
const s0 = Color.interpolate(s00, s10, yd);
const s1 = Color.interpolate(s01, s11, yd);
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];
Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3);
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);
}
}
}
return image;
}
//
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);
}
}
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);
}
}

View File

@@ -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'),
};
}
}

View File

@@ -245,7 +245,7 @@ export namespace Text {
...BaseGeometry.createValues(props, counts),
uSizeFactor: ValueCell.create(props.sizeFactor),
uBorderWidth: ValueCell.create(clamp(props.borderWidth / 2, 0, 0.5)),
uBorderWidth: ValueCell.create(clamp(props.borderWidth, 0, 0.5)),
uBorderColor: ValueCell.create(Color.toArrayNormalized(props.borderColor, Vec3.zero(), 0)),
uOffsetX: ValueCell.create(props.offsetX),
uOffsetY: ValueCell.create(props.offsetY),

View File

@@ -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');
}
const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
accumulateTexture.define(width, height);
const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
//
const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
if (!webgl.namedFramebuffers[ColorAccumulateName]) {
webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
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;
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);
}

View File

@@ -40,7 +40,7 @@ export interface TextureMesh {
readonly boundingSphere: Sphere3D
meta?: unknown
readonly meta: { [k: string]: unknown }
}
export namespace TextureMesh {
@@ -92,6 +92,7 @@ export namespace TextureMesh {
normalTexture: ValueCell.create(normalTexture),
doubleBuffer: new DoubleBuffer(),
boundingSphere: Sphere3D.clone(boundingSphere),
meta: {}
};
}
}
@@ -165,6 +166,8 @@ export namespace TextureMesh {
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dGeoTexture: ValueCell.create(true),
meta: ValueCell.create(textureMesh.meta),
};
}

View File

@@ -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'),
};
}
}

View File

@@ -53,7 +53,7 @@ describe('renderer', () => {
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(7);
expect(ctx.stats.resourceCounts.vertexArray).toBe(8);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 8 : 0);
expect(ctx.stats.resourceCounts.program).toBe(8);
expect(ctx.stats.resourceCounts.shader).toBe(16);

View File

@@ -116,6 +116,9 @@ function setRenderingDefaults(ctx: WebGLContext) {
}
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
const { gl, resources, extensions } = ctx;
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
const width = pyramidTex.getWidth();
@@ -173,9 +176,6 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
ctx.state.currentRenderItemId = -1;
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,

View File

@@ -1,17 +1,19 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Texture } from '../../mol-gl/webgl/texture';
import { PrintImageOptions, printTextureImage } from '../../mol-gl/renderable/util';
import { defaults, ValueCell } from '../../mol-util';
import { ValueSpec, AttributeSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { ValueSpec, AttributeSpec, UniformSpec, Values, TextureSpec } from '../../mol-gl/renderable/schema';
import { Vec2 } from '../../mol-math/linear-algebra';
import { GLRenderingContext } from '../../mol-gl/webgl/compat';
import { PixelData } from '../../mol-util/image';
import { ShaderCode } from '../shader-code';
import { copy_frag } from '../shader/copy.frag';
import { quad_vert } from '../shader/quad.vert';
import { createComputeRenderItem } from '../webgl/render-item';
import { ComputeRenderable, createComputeRenderable } from '../renderable';
export const QuadPositions = new Float32Array([
1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // First triangle
@@ -34,21 +36,57 @@ export const QuadValues: Values<typeof QuadSchema> = {
//
function getArrayForTexture(gl: GLRenderingContext, texture: Texture, size: number) {
switch (texture.type) {
case gl.UNSIGNED_BYTE: return new Uint8Array(size);
case gl.FLOAT: return new Float32Array(size);
}
throw new Error('unknown/unsupported texture type');
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
export type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
export function createCopyRenderable(ctx: WebGLContext, texture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(texture),
uTexSize: ValueCell.create(Vec2.create(texture.getWidth(), texture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number): PixelData {
const SharedCopyName = 'shared-copy';
export function getSharedCopyRenderable(ctx: WebGLContext, texture: Texture) {
if (!ctx.namedComputeRenderables[SharedCopyName]) {
ctx.namedComputeRenderables[SharedCopyName] = createCopyRenderable(ctx, createNullTexture());
}
const copy = ctx.namedComputeRenderables[SharedCopyName] as CopyRenderable;
ValueCell.update(copy.values.tColor, texture);
ValueCell.update(copy.values.uTexSize, Vec2.set(copy.values.uTexSize.ref.value, texture.getWidth(), texture.getHeight()));
copy.update();
return copy;
}
//
const ReadTextureName = 'read-texture';
const ReadAlphaTextureName = 'read-alpha-texture';
export function readTexture(ctx: WebGLContext, texture: Texture) {
const { gl, resources } = ctx;
width = defaults(width, texture.getWidth());
height = defaults(height, texture.getHeight());
const size = width * height * 4;
const framebuffer = resources.framebuffer();
const array = getArrayForTexture(gl, texture, size);
if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
if (!ctx.namedFramebuffers[ReadTextureName]) {
ctx.namedFramebuffers[ReadTextureName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[ReadTextureName];
const width = texture.getWidth();
const height = texture.getHeight();
const array = new Uint8Array(width * height * 4);
framebuffer.bind();
texture.attachFramebuffer(framebuffer, 0);
ctx.readPixels(0, 0, width, height, array);
@@ -56,8 +94,44 @@ export function readTexture(ctx: WebGLContext, texture: Texture, width?: number,
return { array, width, height };
}
export function printTexture(ctx: WebGLContext, texture: Texture, options: Partial<PrintImageOptions> = {}) {
const pixelData = readTexture(ctx, texture);
PixelData.flipY(pixelData);
printTextureImage(pixelData, options);
export function readAlphaTexture(ctx: WebGLContext, texture: Texture) {
const { gl, state, resources } = ctx;
if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
const width = texture.getWidth();
const height = texture.getHeight();
const copy = getSharedCopyRenderable(ctx, texture);
state.currentRenderItemId = -1;
if (!ctx.namedFramebuffers[ReadAlphaTextureName]) {
ctx.namedFramebuffers[ReadAlphaTextureName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[ReadAlphaTextureName];
framebuffer.bind();
if (!ctx.namedTextures[ReadAlphaTextureName]) {
ctx.namedTextures[ReadAlphaTextureName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
}
const copyTex = ctx.namedTextures[ReadAlphaTextureName];
copyTex.define(width, height);
copyTex.attachFramebuffer(framebuffer, 0);
state.disable(gl.CULL_FACE);
state.enable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.clearColor(0, 0, 0, 0);
state.blendFunc(gl.ONE, gl.ONE);
state.blendEquation(gl.FUNC_ADD);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
copy.render();
const array = new Uint8Array(width * height * 4);
ctx.readPixels(0, 0, width, height, array);
return { array, width, height };
}

View File

@@ -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>

View File

@@ -7,7 +7,7 @@
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema } from './schema';
import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema, ValueSpec } from './schema';
import { MeshShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
@@ -24,6 +24,7 @@ export const TextureMeshSchema = {
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dGeoTexture: DefineSpec('boolean'),
meta: ValueSpec('unknown')
};
export type TextureMeshSchema = typeof TextureMeshSchema
export type TextureMeshValues = Values<TextureMeshSchema>

View File

@@ -91,7 +91,7 @@ export function printImageData(imageData: ImageData, options: Partial<PrintImage
}
canvas.toBlob(imgBlob => {
const objectURL = URL.createObjectURL(imgBlob);
const objectURL = URL.createObjectURL(imgBlob!);
const existingImg = document.getElementById(o.id) as HTMLImageElement;
const img = existingImg || document.createElement('img');
img.id = o.id;

View File

@@ -320,6 +320,10 @@ namespace Renderer {
}
if (r.values.dRenderMode) { // indicates direct-volume
if ((variant[0] === 'p' || variant === 'depth') && r.values.dRenderMode.ref.value === 'volume') {
return; // no picking/depth in volume mode
}
// culling done in fragment shader
state.disable(gl.CULL_FACE);
state.frontFace(gl.CCW);

View File

@@ -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
`;

View File

@@ -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)

View File

@@ -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

View File

@@ -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);
}
`;

View File

@@ -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
}
`;

View File

@@ -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;
}
`;

View File

@@ -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

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 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>
*/
@@ -11,6 +11,7 @@ import { idFactory } from '../../mol-util/id-factory';
import { ValueOf } from '../../mol-util/type-helpers';
import { GLRenderingContext } from './compat';
import { WebGLExtensions } from './extensions';
import { WebGLState } from './state';
const getNextBufferId = idFactory();
@@ -192,7 +193,7 @@ export interface AttributeBuffer extends Buffer {
bind: (location: number) => void
}
export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
const { instancedArrays } = extensions;
const buffer = createBuffer(gl, array, usageHint, 'attribute');
@@ -204,12 +205,12 @@ export function createAttributeBuffer<T extends ArrayType, S extends AttributeIt
gl.bindBuffer(_bufferType, buffer.getBuffer());
if (itemSize === 16) {
for (let i = 0; i < 4; ++i) {
gl.enableVertexAttribArray(location + i);
state.enableVertexAttrib(location + i);
gl.vertexAttribPointer(location + i, 4, _dataType, false, 4 * 4 * _bpe, i * 4 * _bpe);
instancedArrays.vertexAttribDivisor(location + i, divisor);
}
} else {
gl.enableVertexAttribArray(location);
state.enableVertexAttrib(location);
gl.vertexAttribPointer(location, itemSize, _dataType, false, 0, 0);
instancedArrays.vertexAttribDivisor(location, divisor);
}

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat } from './compat';
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject } from './compat';
import { isDebugMode } from '../../mol-util/debug';
export type WebGLExtensions = {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 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>
*/
@@ -39,12 +39,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
if (spec.type === 'attribute') {
const loc = gl.getAttribLocation(program, k);
// unused attributes will result in a `-1` location which is usually fine
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`);
locations[k] = loc;
} else if (spec.type === 'uniform' || spec.type === 'texture') {
const loc = gl.getUniformLocation(program, k);
// unused uniforms will result in a `null` location which is usually fine
// if (loc === null) console.info(`Could not get uniform location for '${k}'`)
// if (loc === null) console.info(`Could not get uniform location for '${k}'`);
locations[k] = loc as number;
}
});
@@ -192,11 +192,13 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
}
},
bindAttributes: (attributeBuffers: AttributeBuffers) => {
state.clearVertexAttribsState();
for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
const [k, buffer] = attributeBuffers[i];
const l = locations[k];
if (l !== -1) buffer.bind(l);
}
state.disableUnusedVertexAttribs();
},
bindTextures: (textures: Textures, startingTargetUnit: number) => {
for (let i = 0, il = textures.length; i < il; ++i) {

View File

@@ -67,15 +67,9 @@ function createProgramVariant(ctx: WebGLContext, variant: string, defineValues:
//
type ProgramVariants = { [k: string]: Program }
type VertexArrayVariants = { [k: string]: VertexArray | null }
type ProgramVariants = Record<string, Program>
type VertexArrayVariants = Record<string, VertexArray | null>
interface ValueChanges {
attributes: boolean
defines: boolean
elements: boolean
textures: boolean
}
function createValueChanges() {
return {
attributes: false,
@@ -84,6 +78,8 @@ function createValueChanges() {
textures: false,
};
}
type ValueChanges = ReturnType<typeof createValueChanges>
function resetValueChanges(valueChanges: ValueChanges) {
valueChanges.attributes = false;
valueChanges.defines = false;

View File

@@ -114,7 +114,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
return {
attribute: (array: ArrayType, itemSize: AttributeItemSize, divisor: number, usageHint?: UsageHint) => {
return wrap('attribute', createAttributeBuffer(gl, extensions, array, itemSize, divisor, usageHint));
return wrap('attribute', createAttributeBuffer(gl, state, extensions, array, itemSize, divisor, usageHint));
},
elements: (array: ElementsType, usageHint?: UsageHint) => {
return wrap('elements', createElementsBuffer(gl, array, usageHint));
@@ -133,7 +133,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
return wrap('texture', createTexture(gl, extensions, kind, format, type, filter));
},
vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => {
return wrap('vertexArray', createVertexArray(extensions, program, attributeBuffers, elementsBuffer));
return wrap('vertexArray', createVertexArray(gl, extensions, program, attributeBuffers, elementsBuffer));
},
getByteCounts: () => {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 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>
*/
@@ -59,11 +59,15 @@ export type WebGLState = {
/** set the RGB blend equation and alpha blend equation separately, determines how a new pixel is combined with an existing */
blendEquationSeparate: (modeRGB: number, modeAlpha: number) => void
enableVertexAttrib: (index: number) => void
clearVertexAttribsState: () => void
disableUnusedVertexAttribs: () => void
reset: () => void
}
export function createState(gl: GLRenderingContext): WebGLState {
let enabledCapabilities: { [k: number]: boolean } = {};
let enabledCapabilities: Record<number, boolean> = {};
let currentFrontFace = gl.getParameter(gl.FRONT_FACE);
let currentCullFace = gl.getParameter(gl.CULL_FACE_MODE);
@@ -79,6 +83,16 @@ export function createState(gl: GLRenderingContext): WebGLState {
let currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
let currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
let maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
const vertexAttribsState: number[] = [];
const clearVertexAttribsState = () => {
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
}
};
clearVertexAttribsState();
return {
currentProgramId: -1,
currentMaterialId: -1,
@@ -168,6 +182,17 @@ export function createState(gl: GLRenderingContext): WebGLState {
}
},
enableVertexAttrib: (index: number) => {
gl.enableVertexAttribArray(index);
vertexAttribsState[index] = 1;
},
clearVertexAttribsState,
disableUnusedVertexAttribs: () => {
for (let i = 0; i < maxVertexAttribs; ++i) {
if (vertexAttribsState[i] === 0) gl.disableVertexAttribArray(i);
}
},
reset: () => {
enabledCapabilities = {};
@@ -184,6 +209,12 @@ export function createState(gl: GLRenderingContext): WebGLState {
currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
vertexAttribsState.length = 0;
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
}
}
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 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>
*/
@@ -8,6 +8,7 @@ import { Program } from './program';
import { ElementsBuffer, AttributeBuffers } from './buffer';
import { WebGLExtensions } from './extensions';
import { idFactory } from '../../mol-util/id-factory';
import { GLRenderingContext } from './compat';
const getNextVertexArrayId = idFactory();
@@ -40,7 +41,7 @@ export interface VertexArray {
destroy: () => void
}
export function createVertexArray(extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
export function createVertexArray(gl: GLRenderingContext, extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
const id = getNextVertexArrayId();
let vertexArray = getVertexArray(extensions);
let vertexArrayObject = getVertexArrayObject(extensions);
@@ -68,6 +69,13 @@ export function createVertexArray(extensions: WebGLExtensions, program: Program,
},
destroy: () => {
if (destroyed) return;
if (elementsBuffer) {
// workaround for ANGLE/Chromium bug
// - https://bugs.chromium.org/p/angleproject/issues/detail?id=6599
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1272238
vertexArrayObject.bindVertexArray(vertexArray);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
vertexArrayObject.deleteVertexArray(vertexArray);
destroyed = true;
}

View File

@@ -332,6 +332,80 @@ M END
$$$$
`;
const V3000SdfString = `FYI-001
FYICenter.com
123456789012345678901234567890123456789012345678901234567890
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 13 14 0 0 0
M V30 BEGIN ATOM
M V30 1 N 0.84 -0.16 0 0
M V30 2 N 1.48 0.43 0 0
M V30 3 N 0.09 0.27 0 0
M V30 4 C 1.11 1.21 0 0
M V30 5 C 0.27 1.12 0 0
M V30 6 C 0.84 -1.03 0 0
M V30 7 C 1.53 1.99 0 0
M V30 8 Cl 1.07 2.74 0.01 0
M V30 9 C 1.59 -1.46 0 0
M V30 10 C 0.08 -1.46 0 0
M V30 11 C 1.59 -2.33 0 0
M V30 12 C 0.07 -2.32 0 0
M V30 13 C 0.84 -2.76 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 2 1
M V30 2 1 3 1
M V30 3 1 6 1
M V30 4 2 4 2
M V30 5 2 5 3
M V30 6 1 7 4
M V30 7 1 4 5
M V30 8 1 9 6
M V30 9 2 10 6
M V30 10 1 8 7
M V30 11 2 11 9
M V30 12 1 12 10
M V30 13 1 13 11
M V30 14 2 13 12
M V30 END BOND
M V30 END CTAB
M END
> <Comment>
This is an SDF example.
With a multi-line comment.
> <source>
This was retrieved from biotech.fyicenter.com
$$$$
L-Alanine
GSMACCS-II07189510252D 1 0.00366 0.00000 0
Figure 1, J. Chem. Inf. Comput. Sci., Vol 32, No. 3., 1992
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 6 5 0 0 1
M V30 BEGIN ATOM
M V30 1 C -0.6622 0.5342 0 0 CFG=2
M V30 2 C 0.6622 -0.3 0 0
M V30 3 C -0.7207 2.0817 0 0 MASS=13
M V30 4 N -1.8622 -0.3695 0 0 CHG=1
M V30 5 O 0.622 -1.8037 0 0
M V30 6 O 1.9464 0.4244 0 0 CHG=-1
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 1 3 CFG=1
M V30 3 1 1 4
M V30 4 2 2 5
M V30 5 1 2 6
M V30 END BOND
M V30 END CTAB
M END
$$$$
`;
describe('sdf reader', () => {
it('basic', async () => {
const parsed = await parseSdf(SdfString).run();
@@ -383,4 +457,58 @@ describe('sdf reader', () => {
expect(compound3.dataItems.dataHeader.value(21)).toBe('<PUBCHEM_COORDINATE_TYPE>');
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
});
it('v3000', async () => {
const parsed = await parseSdf(V3000SdfString).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
expect(parsed.result.compounds.length).toBe(2);
const compound1 = parsed.result.compounds[0];
expect(compound1.molFile.atoms.count).toBe(13);
expect(compound1.molFile.atoms.x.rowCount).toBe(13);
expect(compound1.molFile.atoms.y.rowCount).toBe(13);
expect(compound1.molFile.atoms.z.rowCount).toBe(13);
expect(compound1.molFile.atoms.type_symbol.rowCount).toBe(13);
expect(compound1.molFile.bonds.count).toBe(14);
expect(compound1.molFile.bonds.atomIdxA.rowCount).toBe(14);
expect(compound1.molFile.bonds.atomIdxB.rowCount).toBe(14);
expect(compound1.molFile.bonds.order.rowCount).toBe(14);
expect(compound1.molFile.atoms.x.value(7)).toBe(1.07);
expect(compound1.molFile.atoms.y.value(7)).toBe(2.74);
expect(compound1.molFile.atoms.z.value(7)).toBe(0.01);
expect(compound1.molFile.atoms.type_symbol.value(7)).toBe('Cl');
expect(compound1.molFile.bonds.atomIdxA.value(10)).toBe(11);
expect(compound1.molFile.bonds.atomIdxB.value(10)).toBe(9);
expect(compound1.molFile.bonds.order.value(10)).toBe(2);
expect(compound1.dataItems.dataHeader.rowCount).toBe(2);
expect(compound1.dataItems.data.rowCount).toBe(2);
expect(compound1.dataItems.dataHeader.value(0)).toBe('<Comment>');
expect(compound1.dataItems.data.value(0)).toBe(`This is an SDF example.\nWith a multi-line comment.`);
expect(compound1.dataItems.dataHeader.value(1)).toBe('<source>');
expect(compound1.dataItems.data.value(1)).toBe('This was retrieved from biotech.fyicenter.com');
const compound2 = parsed.result.compounds[1];
expect(compound2.molFile.atoms.count).toBe(6);
expect(compound2.molFile.bonds.count).toBe(5);
expect(compound2.molFile.atoms.x.value(4)).toBe(0.622);
expect(compound2.molFile.atoms.y.value(4)).toBe(-1.8037);
expect(compound2.molFile.atoms.z.value(4)).toBe(0);
expect(compound2.molFile.atoms.type_symbol.value(4)).toBe('O');
expect(compound2.molFile.bonds.atomIdxA.value(1)).toBe(1);
expect(compound2.molFile.bonds.atomIdxB.value(1)).toBe(3);
expect(compound2.molFile.bonds.order.value(1)).toBe(1);
expect(compound2.dataItems.dataHeader.rowCount).toBe(0);
expect(compound2.dataItems.data.rowCount).toBe(0);
});
});

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
/**
* An identifier for the wwPDB site creating or modifying the molecule.
*/
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
processing_site: Aliased<'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
/**
* The action associated with this audit record.
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -114,7 +114,7 @@ export const CifCore_Schema = {
* Abstracts. This ordering is used in _chemical_formula.moiety
* and _chemical_formula.sum.
*
* _chemical_formula.iupac '[Mo (C O)4 (C18 H33 P)2]'
* _chemical_formula.IUPAC '[Mo (C O)4 (C18 H33 P)2]'
* _chemical_formula.moiety 'C40 H66 Mo O4 P2'
* _chemical_formula.structural '((C O)4 (P (C6 H11)3)2)Mo'
* _chemical_formula.sum 'C40 H66 Mo O4 P2'
@@ -146,7 +146,7 @@ export const CifCore_Schema = {
sum: str,
/**
* Mass corresponding to the formulae _chemical_formula.structural,
* *_iupac, *_moiety or *_sum and, together with the Z value and cell
* *_IUPAC, *_moiety or *_sum and, together with the Z value and cell
* parameters yield the density given as _exptl_crystal.density_diffrn.
*/
weight: float,
@@ -478,7 +478,7 @@ export const CifCore_Schema = {
* A concatenated series of single-letter codes which indicate the
* refinement restraints or constraints applied to this site. This
* item should not be used. It has been replaced by
* _atom_site.refinement_flags_posn, _adp and _occupancy. It is
* _atom_site.refinement_flags_posn, _ADP and _occupancy. It is
* retained in this dictionary only to provide compatibility with
* legacy CIFs.
*/
@@ -505,9 +505,9 @@ export const CifCore_Schema = {
* atomic displacement parameter, U(equiv), in angstroms squared,
* calculated from anisotropic atomic displacement parameters.
*
* U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~ a~j~)]
* U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~.a~j~)]
*
* a = the real-space cell lengths
* a = the real-space cell vectors
* a* = the reciprocal-space cell lengths
* Ref: Fischer, R. X. & Tillmanns, E. (1988). Acta Cryst. C44, 775-776.
*/
@@ -683,12 +683,21 @@ export const CifCore_Aliases = {
'geom_bond.distance': [
'geom_bond_dist',
],
'audit.block_doi': [
'audit_block_DOI',
],
'database_code.cod': [
'database_code_COD',
],
'database_code.csd': [
'database_code_CSD',
],
'database_code.depnum_ccdc_archive': [
'database_code_depnum_CCDC_archive',
],
'database_code.depnum_ccdc_fiz': [
'database_code_depnum_CCDC_fiz',
],
'database_code.icsd': [
'database_code_ICSD',
],
@@ -699,6 +708,7 @@ export const CifCore_Aliases = {
'database_code_NBS',
],
'atom_site.adp_type': [
'atom_site_ADP_type',
'atom_site_thermal_displace_type',
],
'atom_site.label': [

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -0,0 +1,109 @@
import { Column } from '../../../mol-data/db';
import { MolFile } from '../mol/parser';
import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
export function isV3(
versionLine: string
): boolean {
return versionLine.trim().endsWith('V3000');
}
export function handleCountsV3(
tokenizer: Tokenizer
): { atomCount: number, bondCount: number } {
const atomCount = TokenBuilder.create(tokenizer.data, 1);
const bondCount = TokenBuilder.create(tokenizer.data, 1);
Tokenizer.eatLine(tokenizer); // BEGIN CTAB
skipSingleValue(tokenizer); // M
skipSingleValue(tokenizer); // V30
skipSingleValue(tokenizer); // COUNTS
addSingleValue(tokenizer, atomCount);
addSingleValue(tokenizer, bondCount);
Tokenizer.eatLine(tokenizer);
return {
atomCount: TokenColumn(atomCount)(Column.Schema.int).value(0),
bondCount: TokenColumn(bondCount)(Column.Schema.int).value(0)
};
}
export function handleAtomsV3(
tokenizer: Tokenizer,
atomCount: number
): MolFile['atoms'] {
const x = TokenBuilder.create(tokenizer.data, atomCount * 2);
const y = TokenBuilder.create(tokenizer.data, atomCount * 2);
const z = TokenBuilder.create(tokenizer.data, atomCount * 2);
const type_symbol = TokenBuilder.create(tokenizer.data, atomCount * 2);
for (let i = 0; i < atomCount; ++i) {
Tokenizer.markLine(tokenizer);
skipSingleValue(tokenizer); // M
skipSingleValue(tokenizer); // V30
skipSingleValue(tokenizer); // Index
const { position } = tokenizer;
addSingleValue(tokenizer, type_symbol);
addSingleValue(tokenizer, x);
addSingleValue(tokenizer, y);
addSingleValue(tokenizer, z);
tokenizer.position = position;
}
Tokenizer.eatLine(tokenizer); // Previous Line
Tokenizer.eatLine(tokenizer); // END ATOM
return {
count: atomCount,
x: TokenColumn(x)(Column.Schema.float),
y: TokenColumn(y)(Column.Schema.float),
z: TokenColumn(z)(Column.Schema.float),
type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
};
}
export function handleBondsV3(
tokenizer: Tokenizer,
bondCount: number
): MolFile['bonds'] {
const atomIdxA = TokenBuilder.create(tokenizer.data, bondCount * 2);
const atomIdxB = TokenBuilder.create(tokenizer.data, bondCount * 2);
const order = TokenBuilder.create(tokenizer.data, bondCount * 2);
for (let i = 0; i < bondCount; ++i) {
Tokenizer.markLine(tokenizer);
skipSingleValue(tokenizer); // M
skipSingleValue(tokenizer); // V30
skipSingleValue(tokenizer); // Index
const { position } = tokenizer;
addSingleValue(tokenizer, order);
addSingleValue(tokenizer, atomIdxA);
addSingleValue(tokenizer, atomIdxB);
tokenizer.position = position;
}
Tokenizer.eatLine(tokenizer); // Previous Line
Tokenizer.eatLine(tokenizer); // END BOND
return {
count: bondCount,
atomIdxA: TokenColumn(atomIdxA)(Column.Schema.float),
atomIdxB: TokenColumn(atomIdxB)(Column.Schema.float),
order: TokenColumn(order)(Column.Schema.float),
};
}
function skipSingleValue(tokenizer: Tokenizer) {
Tokenizer.skipWhitespace(tokenizer);
Tokenizer.eatValue(tokenizer);
}
function addSingleValue(tokenizer: Tokenizer, tokens: Tokens) {
const { position: valueStart } = tokenizer;
Tokenizer.skipWhitespace(tokenizer);
Tokenizer.eatValue(tokenizer);
Tokenizer.trim(tokenizer, valueStart, tokenizer.position);
TokenBuilder.addUnchecked(tokens, tokenizer.tokenStart, tokenizer.tokenEnd);
}

View File

@@ -11,8 +11,9 @@ import { Task } from '../../../mol-task';
import { ReaderResult as Result } from '../result';
import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
import { handleAtomsV3, handleBondsV3, handleCountsV3, isV3 } from './parser-v3-util';
/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 */
/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 & 79 */
export interface SdfFileCompound {
readonly molFile: MolFile,
@@ -66,14 +67,22 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
};
}
function handleCountsV2(countsAndVersion: string): { atomCount: number, bondCount: number } {
return {
atomCount: +countsAndVersion.substr(0, 3),
bondCount: +countsAndVersion.substr(3, 3)
};
}
function handleMolFile(tokenizer: Tokenizer) {
const title = Tokenizer.readLine(tokenizer).trim();
const program = Tokenizer.readLine(tokenizer).trim();
const comment = Tokenizer.readLine(tokenizer).trim();
const counts = Tokenizer.readLine(tokenizer);
const countsAndVersion = Tokenizer.readLine(tokenizer);
const molIsV3 = isV3(countsAndVersion);
const atomCount = +counts.substr(0, 3), bondCount = +counts.substr(3, 3);
const { atomCount, bondCount } = molIsV3 ? handleCountsV3(tokenizer) : handleCountsV2(countsAndVersion);
if (Number.isNaN(atomCount) || Number.isNaN(bondCount)) {
// try to skip to next molecule
@@ -84,8 +93,8 @@ function handleMolFile(tokenizer: Tokenizer) {
return;
}
const atoms = handleAtoms(tokenizer, atomCount);
const bonds = handleBonds(tokenizer, bondCount);
const atoms = molIsV3 ? handleAtomsV3(tokenizer, atomCount) : handleAtoms(tokenizer, atomCount);
const bonds = molIsV3 ? handleBondsV3(tokenizer, bondCount) : handleBonds(tokenizer, bondCount);
const dataItems = handleDataItems(tokenizer);
return {

View File

@@ -1,5 +1,5 @@
/**
* 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>
* @author Fred Ludlow <Fred.Ludlow@astx.com>
@@ -68,11 +68,8 @@ function addUnitHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder:
const { totalH } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const { elementAromaticRingIndices } = unit.rings;
for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
if (elementAromaticRingIndices.has(i)) continue;
const element = typeSymbol(unit, i);
if ((
// include both nitrogen atoms in histidine due to
@@ -134,15 +131,12 @@ function addUnitHydrogenAcceptors(structure: Structure, unit: Unit.Atomic, build
const { charge, implicitH, idealGeometry } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const { elementAromaticRingIndices } = unit.rings;
const add = (i: StructureElement.UnitIndex) => {
builder.add(FeatureType.HydrogenAcceptor, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
};
for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
if (elementAromaticRingIndices.has(i)) continue;
const element = typeSymbol(unit, i);
if (element === Elements.O) {
// Basically assume all oxygen atoms are acceptors!

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -99,12 +99,12 @@ const residue = {
secondary_structure_type: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
return secStruc ? secStruc.type[secStruc.getIndex(l.unit.residueIndex[l.element])] : SecondaryStructureType.Flag.NA;
}),
secondary_structure_key: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
return secStruc ? secStruc.key[secStruc.getIndex(l.unit.residueIndex[l.element])] : -1;
}),
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
};

View File

@@ -355,8 +355,8 @@ class Structure {
return this.models.indexOf(m);
}
remapModel(m: Model) {
const { dynamicBonds, interUnitBonds } = this.state;
remapModel(m: Model): Structure {
const { dynamicBonds, interUnitBonds, parent } = this.state;
const units: Unit[] = [];
for (const ug of this.unitSymmetryGroups) {
const unit = ug.units[0].remapModel(m, dynamicBonds);
@@ -367,6 +367,7 @@ class Structure {
}
}
return Structure.create(units, {
parent: parent?.remapModel(m),
label: this.label,
interUnitBonds: dynamicBonds ? undefined : interUnitBonds,
dynamicBonds

View File

@@ -53,7 +53,8 @@ export const OpenFiles = StateAction.build({
await provider.visuals?.(plugin, parsed);
}
} catch (e) {
plugin.log.error(e);
console.error(e);
plugin.log.error(`Error opening file '${file.name}'`);
}
}
}).runInContext(taskCtx);
@@ -88,7 +89,8 @@ export const DownloadFile = StateAction.build({
await provider.visuals?.(plugin, parsed);
}
} catch (e) {
plugin.log.error(e);
console.error(e);
plugin.log.error(`Error downloading '${typeof params.url === 'string' ? params.url : params.url.url}'`);
}
}).runInContext(taskCtx);
}));

View File

@@ -33,7 +33,9 @@ async function tryObtainRecommendedIsoValue(plugin: PluginContext, volume?: Volu
try {
const absIsoLevel = await getContourLevelEmdb(plugin, ctx, entryId);
RecommendedIsoValue.Provider.set(volume, Volume.IsoValue.absolute(absIsoLevel));
} catch (e) { }
} catch (e) {
console.warn(e);
}
}));
}

View File

@@ -1,5 +1,5 @@
/**
* 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>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -18,6 +18,7 @@ import { SetUtils } from '../../mol-util/set';
import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms';
import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
import { SecondaryStructureProvider } from '../../mol-model-props/computed/secondary-structure';
export enum StructureSelectionCategory {
Type = 'Type',
@@ -250,7 +251,12 @@ const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
MS.core.type.bitflags([SecondaryStructureType.Flag.Helix])
])
})
]), { category: StructureSelectionCategory.Structure });
]), {
category: StructureSelectionCategory.Structure,
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
return SecondaryStructureProvider.attach(ctx, structure);
}
});
const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
@@ -260,7 +266,12 @@ const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.uni
MS.core.type.bitflags([SecondaryStructureType.Flag.Beta])
])
})
]), { category: StructureSelectionCategory.Structure });
]), {
category: StructureSelectionCategory.Structure,
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
return SecondaryStructureProvider.attach(ctx, structure);
}
});
const water = StructureSelectionQuery('Water', MS.struct.modifier.union([
MS.struct.generator.atomGroups({

View File

@@ -261,7 +261,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
return this.setStateSnapshot(snapshot);
}
} catch (e) {
this.plugin.log.error(`Reading state: ${e}`);
console.error(e);
this.plugin.log.error('Error reading state');
}
}

View File

@@ -308,8 +308,9 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
.apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
textColor: Color.fromRgb(255, 255, 255),
borderColor: Color.fromRgb(0, 0, 0),
borderWidth: 0.5,
textSize: 0.33,
borderWidth: 0.3,
offsetZ: 0.75,
customText: `${order++}`
}, { tags: MeasurementOrderLabelTag });
}

View File

@@ -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)`;

View File

@@ -1,3 +1,5 @@
@use "sass:math";
.msp-control-row {
position: relative;
height: $row-height;
@@ -184,7 +186,7 @@
z-index: 100000;
background: $default-background;
border-top: 1px solid $default-background;
padding-bottom: $control-spacing / 2;
padding-bottom: math.div($control-spacing, 2);
width: 100%;
// input[type=text] {
@@ -195,8 +197,8 @@
.msp-toggle-color-picker-above {
.msp-color-picker {
top: -2 * 32px - 16px - $control-spacing / 2;
height: 2 * 32px + 16px + $control-spacing / 2;
top: -2 * 32px - 16px - math.div($control-spacing, 2);
height: 2 * 32px + 16px + math.div($control-spacing, 2);
}
}
@@ -208,10 +210,6 @@
}
.msp-control-offset {
// border-left-width: $control-spacing / 2;
// border-left-style: solid;
// border-left-color: color-increase-contrast($default-background, 10%);
// padding-left: 1px;
padding-left: $control-spacing;
}
@@ -228,7 +226,7 @@
// }
.msp-control-group-wrapper {
//border-left-width: $control-spacing / 2;
//border-left-width: math.div($control-spacing, 2);
//border-left-style: solid;
//border-left-color: color-increase-contrast($default-background, 10%);
@@ -240,10 +238,10 @@
.msp-control-group-header {
background: $default-background;
> button, div {
padding-left: 4px; // $control-spacing / 2 !important;
padding-left: 4px; // math.div($control-spacing, 2) !important;
text-align: left;
height: 24px !important; // 2 * $row-height / 3 !important;
line-height: 24px !important; // 2 * $row-height / 3 !important;
height: 24px !important;
line-height: 24px !important;
font-size: 85% !important;
background: $default-background !important;
color: color-lower-contrast($font-color, 15%);
@@ -253,8 +251,8 @@
line-height: 24px !important;
}
> span {
padding-left: $control-spacing / 2;
line-height: 2 * $row-height / 3;
padding-left: math.div($control-spacing, 2);
line-height: math.div(2 * $row-height, 3);
font-size: 70%;
background: $default-background;
color: color-lower-contrast($font-color, 15%);
@@ -267,7 +265,7 @@
.msp-control-group-footer {
background: color-increase-contrast($default-background, 5%);
height: $control-spacing / 2;
height: math.div($control-spacing, 2);
font-size: 1px;
margin-top: 1px;
}
@@ -339,7 +337,7 @@
margin-top: 1px;
> div {
padding: ($control-spacing / 2) $control-spacing;
padding: (math.div($control-spacing, 2)) $control-spacing;
text-align: left;
color: color-lower-contrast($font-color, 15%);
}
@@ -359,7 +357,7 @@
height: $control-spacing * 3;
> span {
padding: $control-spacing / 2;
padding: math.div($control-spacing, 2);
color: white;
font-weight: bold;
background-color: rgba(0, 0, 0, 0.2);
@@ -370,7 +368,7 @@
.msp-table-legend {
> div {
// min-width: 60px;
margin-right: $control-spacing / 2;
margin-right: math.div($control-spacing, 2);
display: inline-flex;
.msp-table-legend-color {
@@ -379,7 +377,7 @@
}
.msp-table-legend-text {
margin: 0 ($control-spacing / 2);
margin: 0 (math.div($control-spacing, 2));
}
}
}

View File

@@ -1,4 +1,6 @@
@use "sass:math";
.msp-toast-container {
position: relative;
// bottom: $control-spacing;
@@ -75,7 +77,7 @@
bottom: 0;
width: 100%;
text-align: right;
padding-right: $control-spacing / 2;
padding-right: math.div($control-spacing, 2);
}
}
}

View File

@@ -157,7 +157,8 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
this.setState({ busy: true });
try {
await this.applyAction();
} catch {
} catch (e) {
console.error(e);
// eat errors because they should be handled elsewhere
} finally {
this.props.onApply?.();

View File

@@ -246,7 +246,8 @@ export class RemoteStateSnapshots extends PluginUIComponent<
if (this._mounted) this.setState({ entries: entries.asImmutable(), isBusy: false });
} catch (e) {
this.plugin.log.error('Fetching Remote Snapshots: ' + e);
console.error(e);
this.plugin.log.error('Error fetching remote snapshots');
if (this._mounted) this.setState({ entries: OrderedMap(), isBusy: false });
}
}
@@ -294,7 +295,9 @@ export class RemoteStateSnapshots extends PluginUIComponent<
try {
await fetch(entry.removeUrl);
} catch { }
} catch (e) {
console.error(e);
}
}
render() {

View File

@@ -401,7 +401,8 @@ function ResidueListSelectionHelper({ modifier, plugin, close }: { modifier: Str
const query = compileIdListSelection(state.identifiers, state.idType);
plugin.managers.structure.selection.fromCompiledQuery(modifier, query, false);
} catch (e) {
plugin.log.error(`Failed to create selection: ${e}`);
console.error(e);
plugin.log.error('Failed to create selection');
}
};

View File

@@ -199,8 +199,7 @@ export class PluginContext {
const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit, preferWebGl1 });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
}
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
this.canvas3dInit.next(true);

View File

@@ -23,6 +23,8 @@ import { BaseGeometry } from '../mol-geo/geometry/base';
import { Visual } from './visual';
import { CustomProperty } from '../mol-model-props/common/custom-property';
import { Clipping } from '../mol-theme/clipping';
import { SetUtils } from '../mol-util/set';
import { cantorPairing } from '../mol-data/util';
export type RepresentationProps = { [k: string]: any }
@@ -143,6 +145,7 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
/** Number of addressable groups in all visuals of the representation */
readonly groupCount: number
readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
readonly geometryVersion: number
readonly props: Readonly<PD.Values<P>>
readonly params: Readonly<P>
readonly state: Readonly<S>
@@ -215,7 +218,7 @@ namespace Representation {
export type Any = Representation<any, any, any>
export const Empty: Any = {
label: '', groupCount: 0, renderObjects: [], props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
label: '', groupCount: 0, renderObjects: [], geometryVersion: -1, props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
createOrUpdate: () => Task.constant('', undefined),
setState: () => {},
setTheme: () => {},
@@ -226,9 +229,32 @@ namespace Representation {
export type Def<D, P extends PD.Params = {}, S extends State = State> = { [k: string]: RepresentationFactory<D, P, S> }
export class GeometryState {
private curr = new Set<number>();
private next = new Set<number>();
private _version = -1;
get version() {
return this._version;
}
add(id: number, version: number) {
this.next.add(cantorPairing(id, version));
}
snapshot() {
if (!SetUtils.areEqual(this.curr, this.next)) {
this._version += 1;
}
[this.curr, this.next] = [this.next, this.curr];
this.next.clear();
}
}
export function createMulti<D, P extends PD.Params = {}, S extends State = State>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>, stateBuilder: StateBuilder<S>, reprDefs: Def<D, P>): Representation<D, P, S> {
let version = 0;
const updated = new Subject<number>();
const geometryState = new GeometryState();
const currentState = stateBuilder.create();
let currentTheme = Theme.createEmpty();
@@ -271,6 +297,7 @@ namespace Representation {
}
return renderObjects;
},
get geometryVersion() { return geometryState.version; },
get props() { return currentProps; },
get params() { return currentParams; },
createOrUpdate: (props: Partial<P> = {}, data?: D) => {
@@ -288,7 +315,9 @@ namespace Representation {
if (!visuals || visuals.includes(reprMap[i])) {
await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime);
}
geometryState.add(i, reprList[i].geometryVersion);
}
geometryState.snapshot();
updated.next(version++);
});
},
@@ -314,7 +343,7 @@ namespace Representation {
setState: (state: Partial<S>) => {
stateBuilder.update(currentState, state);
for (let i = 0, il = reprList.length; i < il; ++i) {
reprList[i].setState(currentState);
reprList[i].setState(state); // only set the new (partial) state
}
},
setTheme: (theme: Theme) => {
@@ -334,6 +363,7 @@ namespace Representation {
export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation<GraphicsRenderObject, BaseGeometry.Params> {
let version = 0;
const updated = new Subject<number>();
const geometryState = new GeometryState();
const currentState = Representation.createState();
const currentTheme = Theme.createEmpty();
@@ -345,6 +375,7 @@ namespace Representation {
updated,
get groupCount() { return renderObject.values.uGroupCount.ref.value; },
get renderObjects() { return [renderObject]; },
get geometryVersion() { return geometryState.version; },
get props() { return currentProps; },
get params() { return currentParams; },
createOrUpdate: (props: Partial<PD.Values<BaseGeometry.Params>> = {}) => {
@@ -353,6 +384,8 @@ namespace Representation {
return Task.create(`Updating '${label}' representation`, async runtime => {
// TODO
geometryState.add(0, renderObject.id);
geometryState.snapshot();
updated.next(version++);
});
},

View File

@@ -44,6 +44,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
const renderObjects: GraphicsRenderObject<G['kind']>[] = [];
let _renderObject: GraphicsRenderObject<G['kind']> | undefined;
let _shape: Shape<G>;
let geometryVersion = -1;
const _theme = Theme.createEmpty();
let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P); // TODO avoid casting
let currentParams: P;
@@ -157,6 +158,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
}
currentProps = newProps;
if (updateState.createGeometry || updateState.createNew) {
geometryVersion += 1;
}
// increment version
updated.next(version++);
});
@@ -178,6 +182,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
get state() { return _state; },
get theme() { return _theme; },
renderObjects,
get geometryVersion() { return geometryVersion; },
updated,
createOrUpdate,
getLoci(pickingId?: PickingId) {

View File

@@ -7,7 +7,7 @@
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ComplexVisual, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
import { RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Structure, StructureElement, Bond } from '../../mol-model/structure';
import { Subject } from 'rxjs';
import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
@@ -26,6 +26,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
let version = 0;
const { webgl } = ctx;
const updated = new Subject<number>();
const geometryState = new Representation.GeometryState();
const materialId = getNextMaterialId();
const renderObjects: GraphicsRenderObject[] = [];
const _state = StructureRepresentationStateBuilder.create();
@@ -59,9 +60,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
if (newVisual) setState(_state); // current state for new visual
// update list of renderObjects
renderObjects.length = 0;
if (visual && visual.renderObject) renderObjects.push(visual.renderObject);
if (visual && visual.renderObject) {
renderObjects.push(visual.renderObject);
geometryState.add(visual.renderObject.id, visual.geometryVersion);
}
geometryState.snapshot();
// increment version
updated.next(version++);
version += 1;
updated.next(version);
});
}
@@ -100,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
@@ -138,6 +144,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
get params() { return _params; },
get state() { return _state; },
get theme() { return _theme; },
get geometryVersion() { return geometryState.version; },
renderObjects,
updated,
createOrUpdate,

View File

@@ -80,6 +80,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
let currentStructure: Structure;
let geometry: G;
let geometryVersion = -1;
let locationIt: LocationIterator;
let positionIt: LocationIterator;
@@ -187,7 +188,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
currentProps = newProps;
currentTheme = newTheme;
currentStructure = newStructure;
if (newGeometry) geometry = newGeometry;
if (newGeometry) {
geometry = newGeometry;
geometryVersion += 1;
}
}
function lociIsSuperset(loci: Loci) {
@@ -216,6 +220,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
return {
get groupCount() { return locationIt ? locationIt.count : 0; },
get renderObject() { return locationIt && locationIt.count ? renderObject : undefined; },
get geometryVersion() { return geometryVersion; },
createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, structure?: Structure) {
prepareUpdate(theme, props, structure || currentStructure);
if (updateState.createGeometry) {
@@ -253,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);

View File

@@ -8,7 +8,7 @@
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
import { Visual } from '../visual';
import { RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Structure, Unit, StructureElement, Bond } from '../../mol-model/structure';
import { Subject } from 'rxjs';
import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
@@ -34,6 +34,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
const updated = new Subject<number>();
const materialId = getNextMaterialId();
const renderObjects: GraphicsRenderObject[] = [];
const geometryState = new Representation.GeometryState();
const _state = StructureRepresentationStateBuilder.create();
let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>();
@@ -170,8 +171,12 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
// update list of renderObjects
renderObjects.length = 0;
visuals.forEach(({ visual }) => {
if (visual.renderObject) renderObjects.push(visual.renderObject);
if (visual.renderObject) {
renderObjects.push(visual.renderObject);
geometryState.add(visual.renderObject.id, visual.geometryVersion);
}
});
geometryState.snapshot();
// set new structure
if (structure) _structure = structure;
// increment version
@@ -218,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) {
@@ -239,20 +244,14 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
if (visible !== _state.visible) newState.visible = visible;
if (alphaFactor !== _state.alphaFactor) newState.alphaFactor = alphaFactor;
if (pickable !== _state.pickable) newState.pickable = pickable;
if (overpaint !== undefined && !Overpaint.areEqual(overpaint, _state.overpaint)) {
if (_structure) {
newState.overpaint = Overpaint.remap(overpaint, _structure);
}
if (overpaint !== undefined && _structure) {
newState.overpaint = Overpaint.remap(overpaint, _structure);
}
if (transparency !== undefined && !Transparency.areEqual(transparency, _state.transparency)) {
if (_structure) {
newState.transparency = Transparency.remap(transparency, _structure);
}
if (transparency !== undefined && _structure) {
newState.transparency = Transparency.remap(transparency, _structure);
}
if (clipping !== undefined && !Clipping.areEqual(clipping, _state.clipping)) {
if (_structure) {
newState.clipping = Clipping.remap(clipping, _structure);
}
if (clipping !== undefined && _structure) {
newState.clipping = Clipping.remap(clipping, _structure);
}
if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) {
newState.transform = transform;
@@ -287,6 +286,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
});
return groupCount;
},
get geometryVersion() { return geometryState.version; },
get props() { return _props; },
get params() { return _params; },
get state() { return _state; },

View File

@@ -84,6 +84,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
let currentStructureGroup: StructureGroup;
let geometry: G;
let geometryVersion = -1;
let locationIt: LocationIterator;
let positionIt: LocationIterator;
@@ -235,7 +236,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
currentProps = newProps;
currentTheme = newTheme;
currentStructureGroup = newStructureGroup;
if (newGeometry) geometry = newGeometry;
if (newGeometry) {
geometry = newGeometry;
geometryVersion += 1;
}
}
function _createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<P>, geometry?: G) {
@@ -270,6 +274,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
return {
get groupCount() { return locationIt ? locationIt.count : 0; },
get renderObject() { return locationIt && locationIt.count ? renderObject : undefined; },
get geometryVersion() { return geometryVersion; },
createOrUpdate(ctx: VisualContext, theme: Theme, props: PD.Values<P>, structureGroup?: StructureGroup) {
prepareUpdate(theme, props, structureGroup || currentStructureGroup);
if (updateState.createGeometry) {
@@ -318,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);

View File

@@ -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;
@@ -233,7 +235,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
(surface.meta as GaussianSurfaceMeta) = { resolution: densityTextureData.resolution };
(surface.meta as GaussianSurfaceMeta).resolution = densityTextureData.resolution;
return surface;
}
@@ -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;
@@ -309,7 +311,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
(surface.meta as GaussianSurfaceMeta) = { resolution: densityTextureData.resolution };
(surface.meta as GaussianSurfaceMeta).resolution = densityTextureData.resolution;
return surface;
}
@@ -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;

View File

@@ -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,
@@ -58,7 +59,7 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius + getUnitExtraRadius(unit));
surface.setBoundingSphere(sphere);
(surface.meta.resolution as MolecularSurfaceMeta['resolution']) = resolution;
(surface.meta as MolecularSurfaceMeta).resolution = resolution;
return surface;
}
@@ -87,10 +88,10 @@ 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.colorTexture as MolecularSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
(geometry.meta as MolecularSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
}
},
dispose: (geometry: Mesh) => {

View File

@@ -27,8 +27,9 @@ import { StructureGroup } from './util/common';
export const PolymerTraceMeshParams = {
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }),
arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }),
tubularHelices: PD.Boolean(false),
arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Size factor for sheet arrows' }),
tubularHelices: PD.Boolean(false, { description: 'Draw alpha helices as tubes' }),
helixProfile: PD.Select('elliptical', PD.arrayToOptions(['elliptical', 'rounded', 'square'] as const), { description: 'Protein and nucleic helix trace profile' }),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo)
@@ -42,7 +43,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Mesh.createEmpty(mesh);
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor, tubularHelices } = props;
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor, tubularHelices, helixProfile } = props;
const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2;
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh);
@@ -131,9 +132,6 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
h0 = w0 * aspectRatio;
h1 = w1 * aspectRatio;
h2 = w2 * aspectRatio;
[w0, h0] = [h0, w0];
[w1, h1] = [h1, w1];
[w2, h2] = [h2, w2];
} else {
h0 = w0;
h1 = w1;
@@ -142,18 +140,26 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
interpolateSizes(state, w0, w1, w2, h0, h1, h2, shift);
const [normals, binormals] = isNucleicType && !v.isCoarseBackbone ? [binormalVectors, normalVectors] : [normalVectors, binormalVectors];
if (isNucleicType && !v.isCoarseBackbone) {
// TODO: find a cleaner way to swap normal and binormal for nucleic types
for (let i = 0, il = normals.length; i < il; i++) normals[i] *= -1;
}
if (radialSegments === 2) {
if (isNucleicType && !v.isCoarseBackbone) {
// TODO find a cleaner way to swap normal and binormal for nucleic types
for (let i = 0, il = binormalVectors.length; i < il; i++) binormalVectors[i] *= -1;
addRibbon(builderState, curvePoints, binormalVectors, normalVectors, segmentCount, heightValues, widthValues, 0);
addRibbon(builderState, curvePoints, normals, binormals, segmentCount, heightValues, widthValues, 0);
} else {
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0);
addRibbon(builderState, curvePoints, normals, binormals, segmentCount, widthValues, heightValues, 0);
}
} else if (radialSegments === 4) {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap);
addSheet(builderState, curvePoints, normals, binormals, segmentCount, widthValues, heightValues, 0, startCap, endCap);
} else if (h1 === w1) {
addTube(builderState, curvePoints, normals, binormals, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap, 'elliptical');
} else if (helixProfile === 'square') {
addSheet(builderState, curvePoints, normals, binormals, segmentCount, widthValues, heightValues, 0, startCap, endCap);
} else {
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap);
addTube(builderState, curvePoints, normals, binormals, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap, helixProfile);
}
}
@@ -189,7 +195,8 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace
newProps.linearSegments !== currentProps.linearSegments ||
newProps.radialSegments !== currentProps.radialSegments ||
newProps.aspectRatio !== currentProps.aspectRatio ||
newProps.arrowFactor !== currentProps.arrowFactor
newProps.arrowFactor !== currentProps.arrowFactor ||
newProps.helixProfile !== currentProps.helixProfile
);
const secondaryStructureHash = SecondaryStructureProvider.get(newStructureGroup.structure).version;

View File

@@ -93,7 +93,7 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
} else if (radialSegments === 4) {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap);
} else {
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap);
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap, 'elliptical');
}
++i;

View File

@@ -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);
}

View File

@@ -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
@@ -35,6 +40,7 @@ interface Visual<D, P extends PD.Params> {
/** Number of addressable groups in all instances of the visual */
readonly groupCount: number
readonly renderObject: GraphicsRenderObject | undefined
readonly geometryVersion: number
createOrUpdate: (ctx: VisualContext, theme: Theme, props: PD.Values<P>, data?: D) => Promise<void> | void
getLoci: (pickingId: PickingId) => Loci
mark: (loci: Loci, action: MarkerAction) => boolean
@@ -43,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
@@ -133,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
@@ -158,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
@@ -184,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) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 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>
*/
@@ -74,6 +74,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
let currentVolume: Volume;
let geometry: G;
let geometryVersion = -1;
let locationIt: LocationIterator;
let positionIt: LocationIterator;
@@ -156,7 +157,10 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
currentProps = newProps;
currentTheme = newTheme;
currentVolume = newVolume;
if (newGeometry) geometry = newGeometry;
if (newGeometry) {
geometry = newGeometry;
geometryVersion += 1;
}
}
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
@@ -170,6 +174,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
return {
get groupCount() { return locationIt ? locationIt.count : 0; },
get renderObject() { return renderObject; },
get geometryVersion() { return geometryVersion; },
async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, volume?: Volume) {
prepareUpdate(theme, props, volume || currentVolume);
if (updateState.createGeometry) {
@@ -236,6 +241,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
let version = 0;
const { webgl } = ctx;
const updated = new Subject<number>();
const geometryState = new Representation.GeometryState();
const materialId = getNextMaterialId();
const renderObjects: GraphicsRenderObject[] = [];
const _state = Representation.createState();
@@ -266,7 +272,11 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
if (promise) await promise;
// update list of renderObjects
renderObjects.length = 0;
if (visual && visual.renderObject) renderObjects.push(visual.renderObject);
if (visual && visual.renderObject) {
renderObjects.push(visual.renderObject);
geometryState.add(visual.renderObject.id, visual.geometryVersion);
}
geometryState.snapshot();
// increment version
updated.next(version++);
});
@@ -304,6 +314,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
get params() { return _params; },
get state() { return _state; },
get theme() { return _theme; },
get geometryVersion() { return geometryState.version; },
renderObjects,
updated,
createOrUpdate,

View File

@@ -20,7 +20,6 @@ import { now, formatTimespan } from '../mol-util/now';
import { ParamDefinition } from '../mol-util/param-definition';
import { StateTreeSpine } from './tree/spine';
import { AsyncQueue } from '../mol-util/async-queue';
import { isProductionMode } from '../mol-util/debug';
import { arraySetAdd, arraySetRemove } from '../mol-util/array';
import { UniqueArray } from '../mol-data/generic';
import { assignIfUndefined } from '../mol-util/object';
@@ -207,14 +206,18 @@ class State {
if (!restored) {
restored = true;
await this.updateTree(snapshot).runInContext(ctx);
this.events.log.next(LogEntry.error('' + e));
this.events.log.next(LogEntry.error('Error during state transaction, reverting'));
}
if (isNested) {
this.inTransactionError = true;
throw e;
}
if (options?.rethrowErrors) throw e;
if (options?.rethrowErrors) {
throw e;
} else {
console.error(e);
}
} finally {
if (!isNested) {
this.inTransaction = false;
@@ -829,7 +832,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) {
ctx.changed = true;
if (!ctx.hadError) ctx.newCurrent = root;
doError(ctx, root, e, false);
if (!isProductionMode) console.error(e);
console.error(e);
return;
}

View File

@@ -10,7 +10,6 @@ import { Progress } from './progress';
import { now } from '../../mol-util/now';
import { Scheduler } from '../util/scheduler';
import { UserTiming } from '../util/user-timing';
import { isDebugMode } from '../../mol-util/debug';
interface ExposedTask<T> extends Task<T> {
f: (ctx: RuntimeContext) => Promise<T>,
@@ -116,7 +115,6 @@ async function execute<T>(task: ExposedTask<T>, ctx: ObservableRuntimeContext) {
task.onAbort();
}
}
if (isDebugMode) console.error(e);
throw e;
}
}

View File

@@ -61,7 +61,7 @@ export function download(data: Blob | string, downloadName = 'download') {
open(data);
}
} else {
const url = URL.createObjectURL(data);
const url = URL.createObjectURL(typeof data === 'string' ? new Blob([data]) : data);
location.href = url;
setTimeout(() => URL.revokeObjectURL(url), 4E4); // 40s
}

View File

@@ -1,8 +1,17 @@
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VersionFile = require('webpack-version-file-plugin');
const VERSION = require('./package.json').version;
class VersionFilePlugin {
apply() {
fs.writeFileSync(
path.resolve(__dirname, 'lib/mol-plugin/version.js'),
`export var PLUGIN_VERSION = '${VERSION}';\nexport var PLUGIN_VERSION_DATE = new Date(typeof __MOLSTAR_DEBUG_TIMESTAMP__ !== 'undefined' ? __MOLSTAR_DEBUG_TIMESTAMP__ : ${new Date().valueOf()});`);
}
}
const sharedConfig = {
module: {
@@ -36,12 +45,7 @@ const sharedConfig = {
'__MOLSTAR_DEBUG_TIMESTAMP__': webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true)
}),
new MiniCssExtractPlugin({ filename: 'molstar.css' }),
new VersionFile({
extras: { timestamp: `${new Date().valueOf()}` },
packageFile: path.resolve(__dirname, 'package.json'),
templateString: `export var PLUGIN_VERSION = '<%= package.version %>';\nexport var PLUGIN_VERSION_DATE = new Date(typeof __MOLSTAR_DEBUG_TIMESTAMP__ !== 'undefined' ? __MOLSTAR_DEBUG_TIMESTAMP__ : <%= extras.timestamp %>);`,
outputFile: path.resolve(__dirname, 'lib/mol-plugin/version.js')
})
new VersionFilePlugin(),
],
resolve: {
modules: [