Compare commits

...

59 Commits

Author SHA1 Message Date
Alexander Rose
c0f14b7c33 3.0.0-dev.9 2022-01-09 18:24:30 -08:00
Alexander Rose
b096f328fc changelog 2022-01-09 18:19:52 -08:00
Alexander Rose
88dbd43884 re-allow interaction during trackball animation
- was disallowed as a stop-gap measure
- ok after improving temporal multi sampling
2022-01-09 14:39:09 -08:00
Alexander Rose
d61e18e6f3 fix mol2 element symbol assignment 2022-01-09 14:04:31 -08:00
Alexander Rose
ca4a725a79 Merge pull request #336 from molstar/bond-dist-id
IndexPairBonds improvements
2022-01-09 13:37:38 -08:00
Alexander Rose
17a18d5fea tweak bond assignment from IndexPairBonds
- fix & clarify logic
2022-01-09 13:25:14 -08:00
Alexander Rose
73be238ac4 rename IndexPairBonds mapping field from id to key 2022-01-09 12:51:26 -08:00
Alexander Rose
952b320975 Merge branch 'master' of https://github.com/molstar/molstar into bond-dist-id 2022-01-08 13:23:15 -08:00
Alexander Rose
be0f06ff0f fix mol2 crysin support 2022-01-08 13:22:26 -08:00
Alexander Rose
6294ef2db2 fix stats for single element in multi-chain unit
- observe in, e.g., label for water molecule in 3pqr
2022-01-08 12:42:37 -08:00
Alexander Rose
78b5d505bd improve IndexPairBonds
- add id field
- better distance-based assignment
2022-01-08 12:35:40 -08:00
Alexander Rose
22afdffa15 add mol2 symmetry support
- only for spacegroup setting 1
2022-01-08 12:14:14 -08:00
Alexander Rose
d1056eddeb Merge pull request #335 from molstar/standalone-viewer
move Viewer class to separate file
2022-01-08 11:55:53 -08:00
dsehnal
8655f4d85a move viewer app to separate file 2022-01-08 10:31:53 +01:00
Alexander Rose
ada7a45fe6 add PDBj pdb-provider option 2022-01-01 16:56:23 -08:00
Alexander Rose
2d09df55a9 3.0.0-dev.8 2021-12-31 11:42:09 -08:00
Alexander Rose
ec2554537e changelog 2021-12-31 11:37:40 -08:00
Alexander Rose
f266dfadc6 emulate round function for webgl1 compatibility 2021-12-31 11:37:02 -08:00
Alexander Rose
99048eed61 Merge pull request #323 from molstar/dv-refactor
Direct-volume refactor
2021-12-31 10:43:26 -08:00
Alexander Rose
fe63718b0c Merge branch 'master' into dv-refactor 2021-12-31 10:34:50 -08:00
Alexander Rose
2c1200433c Merge pull request #333 from molstar/surface-size-theme
Use size theme in molecular/gaussian surface & label representations
2021-12-31 10:33:46 -08:00
Alexander Rose
4a3252c929 use size theme in label repr 2021-12-30 18:17:40 -08:00
Alexander Rose
5e052174ee use size theme in molecular/gaussian surface repr 2021-12-30 18:12:53 -08:00
Alexander Rose
cda0966105 filter repr size themes to applicable types 2021-12-30 18:01:02 -08:00
Alexander Rose
c0a9716846 Merge branch 'master' of https://github.com/molstar/molstar into dv-refactor 2021-12-30 17:30:44 -08:00
Alexander Rose
18c7395f9d Merge pull request #326 from molstar/marker-fixes
Marker improvements
2021-12-30 17:29:07 -08:00
Alexander Rose
d3da79f3dd better use of StateSelection API 2021-12-30 17:18:21 -08:00
Alexander Rose
5a215daca4 remove superfluous arg from canvas3d.requestDraw 2021-12-30 17:14:07 -08:00
Alexander Rose
8527a3b3ef change canvas3d draw to use options argument 2021-12-30 17:08:28 -08:00
Alexander Rose
492dc1ba32 Merge branch 'master' of https://github.com/molstar/molstar into marker-fixes 2021-12-30 17:04:17 -08:00
Alexander Rose
305a8ca802 Merge pull request #332 from molstar/fix-331
fix gpu isosurface group id
2021-12-30 17:02:54 -08:00
Alexander Rose
6d2a35494f rename float-packing to number-packing
- more telling name
2021-12-30 17:01:23 -08:00
Alexander Rose
e76a08c73a remove unused en/decodeFloatLog 2021-12-30 16:59:39 -08:00
Alexander Rose
a0fef0c20f rename en/decodeFloatRGB
- since they only work for positive integers
- encodeFloatRGB -> packIntToRGB
- decodeFloatRGB -> unpackRGBToInt
2021-12-30 16:57:50 -08:00
Alexander Rose
605432ddd1 fix glsl decodeFloatRGB 2021-12-30 16:39:06 -08:00
Alexander Rose
0c895071d8 support axis order in direct-volume shader 2021-12-30 15:36:12 -08:00
Alexander Rose
79cd833ae6 cleanup 2021-12-30 14:37:34 -08:00
Alexander Rose
0fee928e37 fix gpu isosurface group id
- was wrong for axis order other than 012
- fixes #331
2021-12-30 13:49:08 -08:00
Alexander Rose
7f698336d7 fix webgl error in volume-streaming behavior
- don't use gpu mc for small volumes
- return empty texture-mesh for volumes of size 1 or 0
2021-12-29 18:51:55 -08:00
Alexander Rose
cef04f192a no picking during camera spinning 2021-12-29 16:54:26 -08:00
Alexander Rose
4087c4c226 changelog 2021-12-29 16:27:43 -08:00
Alexander Rose
87bdcd2372 add prefer-webgl1 & disable-wboit Viewer GET params 2021-12-29 16:03:45 -08:00
Alexander Rose
1dbcc0d7c8 Merge branch 'master' of https://github.com/molstar/molstar into dv-refactor 2021-12-29 15:42:41 -08:00
Alexander Rose
4c93f01c64 improve temporal multi-smaple handling
- add forceOn arg
- instead of letting temporal converge do full sampling
- saves one geometry pass
2021-12-28 20:39:03 -08:00
Alexander Rose
0a18412da0 refactor draw/multiSample .render args 2021-12-28 20:24:16 -08:00
Alexander Rose
b67d16bdc4 enable marking pass by default 2021-12-28 19:47:54 -08:00
Alexander Rose
976542d355 ensure mark changes are rendered (even w/ noDraw)
- schedule using forceNextRender to avoid rendering twice
2021-12-28 19:30:45 -08:00
Alexander Rose
a2e5fda646 improve temporal multi-sample rendering
- don't render all when spinning
- only render all when explicitely requested (new allowMulti arg)
2021-12-28 19:28:36 -08:00
Alexander Rose
41b1b65d5f improve SelectLoci behavior
- only handle direct children of updated objects (no update of same repr multiple times)
2021-12-28 19:25:40 -08:00
Alexander Rose
2ec2d1997f improve StructureSelectionManager.onUpdate
- only process for root structure (processing childs is superfluous)
2021-12-28 19:20:54 -08:00
Alexander Rose
0bc65f3b72 improve visual marking performance
- treat structure-loci as every-loci
2021-12-28 19:17:46 -08:00
Alexander Rose
9ed96b3599 fix getMarkersAverage edge case with small arrays 2021-12-28 19:16:08 -08:00
David Sehnal
b1cf9566f6 Merge pull request #324 from molstar/safari15-wboit
PluginFeatureDetection and disable WBOIT in Safari
2021-12-28 20:46:46 +01:00
dsehnal
983ae4f8c2 PluginFeatureDetection and disable WBOIT in Safari 2021-12-24 11:21:15 +01:00
Alexander Rose
7ce3531cc7 direct-volume define cleanup 2021-12-23 15:35:14 -08:00
Alexander Rose
11f1a7fd1c add general dGeometryType
- remove dRenderMode & dGeoTexture
2021-12-23 15:16:14 -08:00
Alexander Rose
47d7dd4d22 move direct-volume coloring into theme
- add 'direct' color type
- remove color from transfer-function (now only alpha)
- add direct-volume color theme support
- add volume-value color theme
2021-12-23 14:47:17 -08:00
Alexander Rose
8b3c0fd94e Merge branch 'master' of https://github.com/molstar/molstar into dv-refactor 2021-12-20 17:51:38 -08:00
Alexander Rose
aa1f081664 remove direct-volume isosurface render-mode 2021-12-05 14:24:18 -08:00
87 changed files with 1421 additions and 1172 deletions

View File

@@ -6,6 +6,32 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.0.0-dev.9] - 2022-01-09
- Add PDBj as a ``pdb-provider`` option
- Move Viewer APP to a separate file to allow use without importing light theme & index.html
- Add symmetry support for mol2 files (only spacegroup setting 1)
- Fix mol2 files element symbol assignment
- Improve bond assignment from ``IndexPairBonds``
- Add ``key`` field for mapping to source data
- Fix assignment of bonds with unphysical length
- Fix label/stats of single atom selection in multi-chain units
## [v3.0.0-dev.8] - 2021-12-31
- Add ``PluginFeatureDetection`` and disable WBOIT in Safari 15.
- Add ``disable-wboit`` Viewer GET param
- Add ``prefer-webgl1`` Viewer GET param
- [Breaking] Refactor direct-volume rendering
- Remove isosurface render-mode (use GPU MC instead)
- Move coloring into theme (like for other geometries/renderables)
- Add ``direct`` color type
- Remove color from transfer-function (now only alpha)
- Add direct-volume color theme support
- Add volume-value color theme
- [Breaking] Use size theme in molecular/gaussian surface & label representations
- This is breaking because it was hardcoded to ``physical`` internally but the repr size theme default was ``uniform`` (now ``physical``)
## [v3.0.0-dev.7] - 2021-12-20
- Reduce number of created programs/shaders

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "molstar",
"version": "3.0.0-dev.7",
"version": "3.0.0-dev.9",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "molstar",
"version": "3.0.0-dev.7",
"version": "3.0.0-dev.9",
"license": "MIT",
"dependencies": {
"@types/argparse": "^2.0.10",

View File

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

479
src/apps/viewer/app.ts Normal file
View File

@@ -0,0 +1,479 @@
/**
* Copyright (c) 2018-2022 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>
*/
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
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 { PresetStructureRepresentations, 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 { createPluginUI } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
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 { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
};
const DefaultViewerOptions = {
customFormats: CustomFormats as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
export class Viewer {
constructor(public plugin: PluginUIContext) {
}
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
],
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
]
};
const element = typeof elementOrId === 'string'
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI(element, spec, {
onBeforeUIRender: plugin => {
// the preset needs to be added before the UI renders otherwise
// "Download Structure" wont be able to pick it up
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
}
});
return new Viewer(plugin);
}
setRemoteSnapshot(id: string) {
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
}
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'url',
params: {
url: Asset.Url(url),
format: format as any,
isBinary,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const plugin = this.plugin;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
}
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
loadPdb(pdb: string, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb' as const,
params: {
provider: {
id: pdb,
server: {
name: provider,
params: PdbDownloadProvider[provider].defaultValue as any
}
},
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
loadPdbDev(pdbDev: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-dev' as const,
params: {
provider: {
id: pdbDev,
encoding: 'bcif',
},
options: params.source.params.options,
}
}
}));
}
loadEmdb(emdb: string, options?: { detail?: number }) {
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
source: {
name: 'pdb-emd-ds' as const,
params: {
provider: {
id: emdb,
server: provider,
},
detail: options?.detail ?? 3,
}
}
}));
}
loadAlphaFoldDb(afdb: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'alphafolddb' as const,
params: {
id: afdb,
options: {
...params.source.params.options,
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
},
}
}
}));
}
loadModelArchive(id: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'modelarchive' as const,
params: {
id,
options: params.source.params.options,
}
}
}));
}
/**
* @example Load X-ray density from volume server
viewer.loadVolumeFromUrl({
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1.5,
color: 0x3362B2
}, {
type: 'relative',
value: 3,
color: 0x33BB33,
volumeIndex: 1
}, {
type: 'relative',
value: -3,
color: 0xBB3333,
volumeIndex: 1
}], {
entryId: ['2FO-FC', 'FO-FC'],
isLazy: true
});
* *********************
* @example Load EM density from volume server
viewer.loadVolumeFromUrl({
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1,
color: 0x3377aa
}], {
entryId: 'EMD-30210',
isLazy: true
});
*/
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
});
return update.commit();
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build();
for (const iso of isovalues) {
repr
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
});
}
/**
* @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);
}
}
export interface LoadStructureOptions {
representationParams?: StructureRepresentationPresetProvider.CommonParams
}
export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
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
}
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-viewer-auto',
display: {
name: 'Automatic (w/ Annotation)', group: 'Annotation',
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
},
isApplicable(a) {
return (
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
);
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
} else {
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
}
}
});

View File

@@ -56,6 +56,8 @@
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
var pickScale = getParam('pick-scale', '[^&]+').trim();
var pickPadding = getParam('pick-padding', '[^&]+').trim();
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1';
molstar.Viewer.create('app', {
layoutShowControls: !hideControls,
@@ -69,6 +71,8 @@
pixelScale: parseFloat(pixelScale) || 1,
pickScale: parseFloat(pickScale) || 0.25,
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
enableWboit: !disableWboit,
preferWebgl1: preferWebgl1,
}).then(viewer => {
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);

View File

@@ -1,482 +1,12 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
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 { PresetStructureRepresentations, 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 { createPluginUI } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
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 { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import './embedded.html';
import './favicon.ico';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
};
const DefaultViewerOptions = {
customFormats: CustomFormats as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
export class Viewer {
constructor(public plugin: PluginUIContext) {
}
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
],
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
]
};
const element = typeof elementOrId === 'string'
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI(element, spec, {
onBeforeUIRender: plugin => {
// the preset needs to be added before the UI renders otherwise
// "Download Structure" wont be able to pick it up
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
}
});
return new Viewer(plugin);
}
setRemoteSnapshot(id: string) {
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
}
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'url',
params: {
url: Asset.Url(url),
format: format as any,
isBinary,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const plugin = this.plugin;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
}
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
loadPdb(pdb: string, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb' as const,
params: {
provider: {
id: pdb,
server: {
name: provider,
params: PdbDownloadProvider[provider].defaultValue as any
}
},
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
loadPdbDev(pdbDev: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-dev' as const,
params: {
provider: {
id: pdbDev,
encoding: 'bcif',
},
options: params.source.params.options,
}
}
}));
}
loadEmdb(emdb: string, options?: { detail?: number }) {
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
source: {
name: 'pdb-emd-ds' as const,
params: {
provider: {
id: emdb,
server: provider,
},
detail: options?.detail ?? 3,
}
}
}));
}
loadAlphaFoldDb(afdb: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'alphafolddb' as const,
params: {
id: afdb,
options: {
...params.source.params.options,
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
},
}
}
}));
}
loadModelArchive(id: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'modelarchive' as const,
params: {
id,
options: params.source.params.options,
}
}
}));
}
/**
* @example Load X-ray density from volume server
viewer.loadVolumeFromUrl({
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1.5,
color: 0x3362B2
}, {
type: 'relative',
value: 3,
color: 0x33BB33,
volumeIndex: 1
}, {
type: 'relative',
value: -3,
color: 0xBB3333,
volumeIndex: 1
}], {
entryId: ['2FO-FC', 'FO-FC'],
isLazy: true
});
* *********************
* @example Load EM density from volume server
viewer.loadVolumeFromUrl({
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1,
color: 0x3377aa
}], {
entryId: 'EMD-30210',
isLazy: true
});
*/
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
});
return update.commit();
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build();
for (const iso of isovalues) {
repr
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
});
}
/**
* @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);
}
}
export interface LoadStructureOptions {
representationParams?: StructureRepresentationPresetProvider.CommonParams
}
export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
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
}
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-viewer-auto',
display: {
name: 'Automatic (w/ Annotation)', group: 'Annotation',
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
},
isApplicable(a) {
return (
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
);
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
} else {
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
}
}
});
export * from './app';

View File

@@ -97,7 +97,7 @@ export class AlphaOrbitalsExample {
}
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: true });
private selectors?: Selectors = void 0;
private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;

View File

@@ -25,7 +25,7 @@ import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
import { Vec3 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
import { unpackRGBToInt } from '../../mol-util/number-packing';
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
@@ -65,7 +65,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
const r = tSize.array[i * 3];
const g = tSize.array[i * 3 + 1];
const b = tSize.array[i * 3 + 2];
return decodeFloatRGB(r, g, b) / sizeDataFactor;
return unpackRGBToInt(r, g, b) / sizeDataFactor;
}
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
@@ -95,9 +95,9 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
const g = groups[i4 + 1];
const b = groups[i4 + 2];
if (groups instanceof Float32Array) {
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
return unpackRGBToInt(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
}
return decodeFloatRGB(r, g, b);
return unpackRGBToInt(r, g, b);
}
protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {

View File

@@ -223,7 +223,7 @@ interface Canvas3D {
clear(): void
syncVisibility(): void
requestDraw(force?: boolean): void
requestDraw(): void
/** Reset the timers, used by "animate" */
resetTime(t: number): void
@@ -355,17 +355,24 @@ namespace Canvas3D {
changed = helper.camera.mark(loci, action) || changed;
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed && !noDraw) {
scene.update(void 0, true);
helper.handle.scene.update(void 0, true);
helper.camera.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw(true);
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
if (changed) {
if (noDraw) {
// Even with `noDraw` make sure changes will be rendered.
// Note that with this calling mark (with or without `noDraw`) multiple times
// during a JS event loop iteration will only result in a single render call.
forceNextRender = true;
} else {
scene.update(void 0, true);
helper.handle.scene.update(void 0, true);
helper.camera.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw({ force: true, allowMulti: true });
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
}
}
}
function render(force: boolean) {
function render(force: boolean, allowMulti: boolean) {
if (webgl.isContextLost) return false;
let resized = false;
@@ -395,14 +402,12 @@ namespace Canvas3D {
cam = stereoCamera;
}
const ctx = { renderer, camera: cam, scene, helper };
if (MultiSamplePass.isEnabled(p.multiSample)) {
if (!cameraChanged) {
while (!multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p));
} else {
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
}
const forceOn = !cameraChanged && allowMulti && !controls.props.spin;
multiSampleHelper.render(ctx, p, true, forceOn);
} else {
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking);
passes.draw.render(ctx, p, true);
}
pickHelper.dirty = true;
didRender = true;
@@ -416,15 +421,15 @@ namespace Canvas3D {
let currentTime = 0;
let drawPaused = false;
function draw(force?: boolean) {
function draw(options?: { force?: boolean, allowMulti?: boolean }) {
if (drawPaused) return;
if (render(!!force) && notifyDidDraw) {
if (render(!!options?.force, !!options?.allowMulti) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
}
function requestDraw(force?: boolean) {
forceNextRender = forceNextRender || !!force;
function requestDraw() {
forceNextRender = true;
}
let animationFrameHandle = 0;
@@ -438,7 +443,7 @@ namespace Canvas3D {
return;
}
draw(false);
draw();
if (!camera.transition.inTransition && !webgl.isContextLost) {
interactionHelper.tick(currentTime);
}
@@ -478,7 +483,7 @@ namespace Canvas3D {
resolveCameraReset();
if (forceDrawAfterAllCommited) {
if (helper.debug.isEnabled) helper.debug.update();
draw(true);
draw({ force: true });
forceDrawAfterAllCommited = false;
}
commited.next(now());
@@ -661,11 +666,11 @@ namespace Canvas3D {
const contextRestoredSub = contextRestored.subscribe(() => {
pickHelper.dirty = true;
draw(true);
draw({ force: true });
// Unclear why, but in Chrome with wboit enabled the first `draw` only clears
// the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
// context loss so it is unclear if it behaves the same.
draw(true);
draw({ force: true });
});
const resized = new BehaviorSubject<any>(0);
@@ -674,7 +679,7 @@ namespace Canvas3D {
passes.updateSize();
updateViewport();
syncViewport();
if (draw) requestDraw(true);
if (draw) requestDraw();
resized.next(+new Date());
}
@@ -699,7 +704,7 @@ namespace Canvas3D {
reprRenderObjects.clear();
scene.clear();
helper.debug.clear();
requestDraw(true);
requestDraw();
reprCount.next(reprRenderObjects.size);
},
syncVisibility: () => {
@@ -711,7 +716,7 @@ namespace Canvas3D {
if (scene.syncVisibility()) {
if (helper.debug.isEnabled) helper.debug.update();
}
requestDraw(true);
requestDraw();
},
requestDraw,
@@ -799,7 +804,7 @@ namespace Canvas3D {
}
if (!doNotRequestDraw) {
requestDraw(true);
requestDraw();
}
},
getImagePass: (props: Partial<ImageProps> = {}) => {

View File

@@ -53,6 +53,19 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
return createComputeRenderable(renderItem, values);
}
type Props = {
postprocessing: PostprocessingProps
marking: MarkingProps
transparentBackground: boolean;
}
type RenderContext = {
renderer: Renderer;
camera: Camera | StereoCamera;
scene: Scene;
helper: Helper;
}
export class DrawPass {
private readonly drawTarget: RenderTarget;
@@ -264,25 +277,25 @@ export class DrawPass {
renderer.renderBlendedTransparent(scene.primitives, camera, null);
}
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, props: Props) {
const volumeRendering = scene.volumes.renderables.length > 0;
const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
const markingEnabled = MarkingPass.isEnabled(markingProps);
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
const antialiasingEnabled = AntialiasingPass.isEnabled(props.postprocessing);
const markingEnabled = MarkingPass.isEnabled(props.marking);
const { x, y, width, height } = camera.viewport;
renderer.setViewport(x, y, width, height);
renderer.update(camera);
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
if (props.transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
this.drawTarget.bind();
renderer.clear(false);
}
if (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
this._renderWboit(renderer, camera, scene, props.transparentBackground, props.postprocessing);
} else {
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, props.transparentBackground, props.postprocessing);
}
if (postprocessingEnabled) {
@@ -294,7 +307,7 @@ export class DrawPass {
}
if (markingEnabled) {
const markingDepthTest = markingProps.ghostEdgeStrength < 1;
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
if (markingDepthTest) {
this.marking.depthTarget.bind();
renderer.clear(false);
@@ -305,7 +318,7 @@ export class DrawPass {
renderer.clear(false);
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
this.marking.update(markingProps);
this.marking.update(props.marking);
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
}
@@ -323,7 +336,7 @@ export class DrawPass {
}
if (antialiasingEnabled) {
this.antialiasing.render(camera, toDrawingBuffer, postprocessingProps);
this.antialiasing.render(camera, toDrawingBuffer, props.postprocessing);
} else if (toDrawingBuffer) {
this.drawTarget.bind();
@@ -338,16 +351,17 @@ export class DrawPass {
this.webgl.gl.flush();
}
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
renderer.setTransparentBackground(transparentBackground);
render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
const { renderer, camera, scene, helper } = ctx;
renderer.setTransparentBackground(props.transparentBackground);
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
renderer.setPixelRatio(this.webgl.pixelRatio);
if (StereoCamera.is(camera)) {
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, props);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, props);
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
this._render(renderer, camera, scene, helper, toDrawingBuffer, props);
}
}

View File

@@ -83,11 +83,12 @@ export class ImagePass {
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height);
this._camera.update();
const ctx = { renderer: this.renderer, camera: this._camera, scene: this.scene, helper: this.helper };
if (MultiSamplePass.isEnabled(this.props.multiSample)) {
this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
this.multiSampleHelper.render(ctx, this.props, false);
this._colorTarget = this.multiSamplePass.colorTarget;
} else {
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking);
this.drawPass.render(ctx, this.props, false);
this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
}
}

View File

@@ -22,7 +22,7 @@ import { Color } from '../../mol-util/color';
import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
export const MarkingParams = {
enabled: PD.Boolean(false),
enabled: PD.Boolean(true),
highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),

View File

@@ -59,6 +59,14 @@ type Props = {
multiSample: MultiSampleProps
postprocessing: PostprocessingProps
marking: MarkingProps
transparentBackground: boolean;
}
type RenderContext = {
renderer: Renderer;
camera: Camera | StereoCamera;
scene: Scene;
helper: Helper;
}
export class MultiSamplePass {
@@ -97,12 +105,12 @@ export class MultiSamplePass {
}
}
render(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
if (props.multiSample.mode === 'temporal') {
return this.renderTemporalMultiSample(sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
render(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean, forceOn: boolean) {
if (props.multiSample.mode === 'temporal' && !forceOn) {
return this.renderTemporalMultiSample(sampleIndex, ctx, props, toDrawingBuffer);
} else {
this.renderMultiSample(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
return sampleIndex;
this.renderMultiSample(ctx, toDrawingBuffer, props);
return -2;
}
}
@@ -114,7 +122,8 @@ export class MultiSamplePass {
}
}
private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
private renderMultiSample(ctx: RenderContext, toDrawingBuffer: boolean, props: Props) {
const { camera } = ctx;
const { compose, composeTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
@@ -148,7 +157,7 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, sampleWeight);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
drawPass.render(ctx, props, false);
// compose rendered scene with compose target
composeTarget.bind();
@@ -181,7 +190,8 @@ export class MultiSamplePass {
camera.update();
}
private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
private renderTemporalMultiSample(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
const { camera } = ctx;
const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
@@ -198,7 +208,7 @@ export class MultiSamplePass {
const sampleWeight = 1.0 / offsetList.length;
if (sampleIndex === -1) {
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
drawPass.render(ctx, props, false);
ValueCell.update(compose.values.uWeight, 1.0);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
@@ -226,7 +236,7 @@ export class MultiSamplePass {
camera.update();
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
drawPass.render(ctx, props, false);
// compose rendered scene with compose target
composeTarget.bind();
@@ -325,8 +335,8 @@ export class MultiSampleHelper {
}
/** Return `true` while more samples are needed */
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean, forceOn?: boolean) {
this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, ctx, props, toDrawingBuffer, !!forceOn);
return this.sampleIndex < 0;
}

View File

@@ -12,7 +12,7 @@ import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { Vec3 } from '../../mol-math/linear-algebra';
import { spiral2d } from '../../mol-math/misc';
import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
import { unpackRGBToInt, unpackRGBAToDepth } from '../../mol-util/number-packing';
import { Camera, ICamera } from '../camera';
import { StereoCamera } from '../camera/stereo';
import { cameraUnproject } from '../camera/util';
@@ -174,7 +174,7 @@ export class PickHelper {
private getId(x: number, y: number, buffer: Uint8Array) {
const idx = this.getBufferIdx(x, y);
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
return unpackRGBToInt(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
}
private render(camera: Camera | StereoCamera) {

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { getMarkersAverage } from '../marker-data';
describe('marker-data', () => {
it('getMarkersAverage', () => {
expect(getMarkersAverage(new Uint8Array([0, 0, 0, 0]), 3)).toBe(0);
expect(getMarkersAverage(new Uint8Array([0, 0, 1, 0]), 3)).toBe(1 / 3);
expect(getMarkersAverage(new Uint8Array([0, 0, 0, 0]), 4)).toBe(0);
expect(getMarkersAverage(new Uint8Array([0, 0, 1, 0]), 4)).toBe(1 / 4);
});
});

View File

@@ -15,7 +15,7 @@ import { LocationColor, ColorTheme } from '../../mol-theme/color';
import { Geometry } from './geometry';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance'
export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance' | 'direct'
export type ColorData = {
uColor: ValueCell<Vec3>,
@@ -50,6 +50,7 @@ function _createColors(locationIt: LocationIterator, positionIt: LocationIterato
case 'vertexInstance': return createVertexInstanceColor(positionIt, colorTheme.color, colorData);
case 'volume': return createGridColor((colorTheme as any).grid, 'volume', colorData);
case 'volumeInstance': return createGridColor((colorTheme as any).grid, 'volumeInstance', colorData);
case 'direct': return createDirectColor(colorData);
}
}
@@ -237,3 +238,25 @@ export function createGridColor(grid: ColorVolume, type: ColorType, colorData?:
};
}
}
//
/** Creates direct color */
function createDirectColor(colorData?: ColorData): ColorData {
if (colorData) {
ValueCell.updateIfChanged(colorData.dColorType, 'direct');
return colorData;
} else {
return {
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uColorGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dColorType: ValueCell.create('direct'),
dUsePalette: ValueCell.create(false),
};
}
}

View File

@@ -213,6 +213,8 @@ export namespace Cylinders {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('cylinders'),
aMapping: cylinders.mappingBuffer,
aGroup: cylinders.groupBuffer,
aStart: cylinders.startBuffer,

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>
*/
@@ -26,8 +26,7 @@ import { TransformData } from '../transform-data';
import { createEmptyTransparency } from '../transparency-data';
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
import { createEmptyClipping } from '../clipping-data';
import { Grid, Volume } from '../../../mol-model/volume';
import { ColorNames } from '../../../mol-util/color/names';
import { Grid } from '../../../mol-model/volume';
import { createEmptySubstance } from '../substance-data';
const VolumeBox = Box();
@@ -48,6 +47,7 @@ export interface DirectVolume {
readonly unitToCartn: ValueCell<Mat4>
readonly cartnToUnit: ValueCell<Mat4>
readonly packedGroup: ValueCell<boolean>
readonly axisOrder: ValueCell<Vec3>
/** Bounding sphere of the volume */
readonly boundingSphere: Sphere3D
@@ -56,10 +56,10 @@ export interface DirectVolume {
}
export namespace DirectVolume {
export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume?: DirectVolume): DirectVolume {
export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume?: DirectVolume): DirectVolume {
return directVolume ?
update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume) :
fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup);
update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume) :
fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder);
}
function hashCode(directVolume: DirectVolume) {
@@ -70,7 +70,7 @@ export namespace DirectVolume {
]);
}
function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean): DirectVolume {
function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3): DirectVolume {
const boundingSphere = Sphere3D();
let currentHash = -1;
@@ -101,6 +101,7 @@ export namespace DirectVolume {
return boundingSphere;
},
packedGroup: ValueCell.create(packedGroup),
axisOrder: ValueCell.create(axisOrder),
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere);
currentHash = hashCode(directVolume);
@@ -109,7 +110,7 @@ export namespace DirectVolume {
return directVolume;
}
function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume: DirectVolume): DirectVolume {
function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume: DirectVolume): DirectVolume {
const width = texture.getWidth();
const height = texture.getHeight();
const depth = texture.getDepth();
@@ -126,6 +127,7 @@ export namespace DirectVolume {
ValueCell.update(directVolume.unitToCartn, unitToCartn);
ValueCell.update(directVolume.cartnToUnit, Mat4.invert(Mat4(), unitToCartn));
ValueCell.updateIfChanged(directVolume.packedGroup, packedGroup);
ValueCell.updateIfChanged(directVolume.axisOrder, Vec3.fromArray(directVolume.axisOrder.ref.value, axisOrder, 0));
return directVolume;
}
@@ -138,47 +140,19 @@ export namespace DirectVolume {
const texture = createNullTexture();
const stats = Grid.One.stats;
const packedGroup = false;
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume);
}
export function createRenderModeParam(stats?: Grid['stats']) {
const isoValueParam = stats
? Volume.createIsoValueParam(Volume.IsoValue.relative(2), stats)
: Volume.IsoValueParam;
return PD.MappedStatic('volume', {
isosurface: PD.Group({
isoValue: isoValueParam,
singleLayer: PD.Boolean(false, { isEssential: true }),
}, { isFlat: true }),
volume: PD.Group({
controlPoints: PD.LineGraph([
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
]),
list: PD.ColorList({
kind: 'interpolate',
colors: [
[ColorNames.white, 0],
[ColorNames.red, 0.25],
[ColorNames.white, 0.5],
[ColorNames.blue, 0.75],
[ColorNames.white, 1]
]
}, { offsets: true }),
}, { isFlat: true })
}, { isEssential: true });
const axisOrder = Vec3.create(0, 1, 2);
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume);
}
export const Params = {
...BaseGeometry.Params,
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
renderMode: createRenderModeParam(),
stepsPerCell: PD.Numeric(5, { min: 1, max: 20, step: 1 }),
controlPoints: PD.LineGraph([
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
], { isEssential: true }),
stepsPerCell: PD.Numeric(3, { min: 1, max: 10, step: 1 }),
jumpLength: PD.Numeric(0, { min: 0, max: 20, step: 0.1 }),
};
export type Params = typeof Params
@@ -217,13 +191,6 @@ export namespace DirectVolume {
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
function getNormalizedIsoValue(out: Vec2, isoValue: Volume.IsoValue, stats: Vec4) {
const [min, max, mean, sigma] = stats;
const value = Volume.IsoValue.toAbsolute(isoValue, { min, max, mean, sigma }).absoluteValue;
Vec2.set(out, (value - min) / (max - min), (0 - min) / (max - min));
return out;
}
function getMaxSteps(gridDim: Vec3, stepsPerCell: number) {
return Math.ceil(Vec3.magnitude(gridDim) * stepsPerCell);
}
@@ -256,18 +223,12 @@ export namespace DirectVolume {
const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const controlPoints = props.renderMode.name === 'volume' ? getControlPointsFromVec2Array(props.renderMode.params.controlPoints) : [];
const transferTex = createTransferFunctionTexture(controlPoints, props.renderMode.name === 'volume' ? props.renderMode.params.list.colors : []);
const isoValue = props.renderMode.name === 'isosurface'
? props.renderMode.params.isoValue
: Volume.IsoValue.relative(2);
const singleLayer = props.renderMode.name === 'isosurface'
? props.renderMode.params.singleLayer
: false;
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
const transferTex = createTransferFunctionTexture(controlPoints);
return {
dGeometryType: ValueCell.create('directVolume'),
...color,
...marker,
...overpaint,
@@ -283,7 +244,6 @@ export namespace DirectVolume {
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
uIsoValue: ValueCell.create(getNormalizedIsoValue(Vec2(), isoValue, directVolume.gridStats.ref.value)),
uBboxMin: bboxMin,
uBboxMax: bboxMax,
uBboxSize: bboxSize,
@@ -292,7 +252,6 @@ export namespace DirectVolume {
uJumpLength: ValueCell.create(props.jumpLength),
uTransform: gridTransform,
uGridDim: gridDimension,
dRenderMode: ValueCell.create(props.renderMode.name),
tTransferTex: transferTex,
uTransferScale: ValueCell.create(getTransferScale(props.stepsPerCell)),
@@ -305,11 +264,8 @@ export namespace DirectVolume {
uCartnToUnit: directVolume.cartnToUnit,
uUnitToCartn: directVolume.unitToCartn,
dPackedGroup: directVolume.packedGroup,
dSingleLayer: ValueCell.create(singleLayer),
dAxisOrder: ValueCell.create(directVolume.axisOrder.ref.value.join('')),
uDoubleSided: ValueCell.create(props.doubleSided),
dFlatShaded: ValueCell.create(props.flatShaded),
dFlipSided: ValueCell.create(props.flipSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
};
@@ -323,20 +279,11 @@ export namespace DirectVolume {
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
BaseGeometry.updateValues(values, props);
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dRenderMode, props.renderMode.name);
if (props.renderMode.name === 'isosurface') {
ValueCell.updateIfChanged(values.uIsoValue, getNormalizedIsoValue(values.uIsoValue.ref.value, props.renderMode.params.isoValue, values.uGridStats.ref.value));
ValueCell.updateIfChanged(values.dSingleLayer, props.renderMode.params.singleLayer);
} else if (props.renderMode.name === 'volume') {
const controlPoints = getControlPointsFromVec2Array(props.renderMode.params.controlPoints);
createTransferFunctionTexture(controlPoints, props.renderMode.params.list.colors, values.tTransferTex);
}
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
createTransferFunctionTexture(controlPoints, values.tTransferTex);
ValueCell.updateIfChanged(values.uMaxSteps, getMaxSteps(values.uGridDim.ref.value, props.stepsPerCell));
ValueCell.updateIfChanged(values.uStepScale, getStepScale(values.uCellDim.ref.value, props.stepsPerCell));
@@ -360,14 +307,14 @@ export namespace DirectVolume {
function createRenderableState(props: PD.Values<Params>): RenderableState {
const state = BaseGeometry.createRenderableState(props);
state.opaque = false;
state.writeDepth = props.renderMode.name === 'isosurface';
state.writeDepth = false;
return state;
}
function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
BaseGeometry.updateRenderableState(state, props);
state.opaque = false;
state.writeDepth = props.renderMode.name === 'isosurface';
state.writeDepth = false;
}
}

View File

@@ -1,16 +1,13 @@
/**
* 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>
*/
import { TextureImage } from '../../../mol-gl/renderable/util';
import { spline } from '../../../mol-math/interpolate';
import { ColorScale } from '../../../mol-util/color';
import { ValueCell } from '../../../mol-util';
import { Vec2 } from '../../../mol-math/linear-algebra';
import { ColorListName } from '../../../mol-util/color/lists';
import { ColorListEntry } from '../../../mol-util/color/color';
export interface ControlPoint { x: number, alpha: number }
@@ -25,7 +22,7 @@ export function getControlPointsFromVec2Array(array: Vec2[]): ControlPoint[] {
return array.map(v => ({ x: v[0], alpha: v[1] }));
}
export function createTransferFunctionTexture(controlPoints: ControlPoint[], listOrName: ColorListEntry[] | ColorListName, texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> {
export function createTransferFunctionTexture(controlPoints: ControlPoint[], texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> {
const cp = [
{ x: 0, alpha: 0 },
{ x: 0, alpha: 0 },
@@ -33,10 +30,9 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], lis
{ x: 1, alpha: 0 },
{ x: 1, alpha: 0 },
];
const scale = ColorScale.create({ domain: [0, 1], listOrName });
const n = 256;
const array = texture ? texture.ref.value.array : new Uint8Array(n * 4);
const array = texture ? texture.ref.value.array : new Uint8Array(n);
let k = 0;
let x1: number, x2: number;
@@ -55,8 +51,7 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], lis
const jl = Math.round((x2 - x1) * n);
for (let j = 0; j < jl; ++j) {
const t = j / jl;
array[k * 4 + 3] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255);
scale.colorToArray(k / 255, array, k * 4);
array[k] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255);
++k;
}
}

View File

@@ -155,6 +155,8 @@ namespace Image {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('image'),
...color,
...marker,
...overpaint,

View File

@@ -220,6 +220,8 @@ export namespace Lines {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('lines'),
aMapping: lines.mappingBuffer,
aGroup: lines.groupBuffer,
aStart: lines.startBuffer,

View File

@@ -47,12 +47,19 @@ export function getMarkersAverage(array: Uint8Array, count: number): number {
const backStart = 4 * viewEnd;
let sum = 0;
for (let i = 0; i < viewEnd; ++i) {
const v = view[i];
sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
}
for (let i = backStart; i < count; ++i) {
sum += array[i] && 1;
if (viewEnd < 0) {
// avoid edge cases with small arrays
for (let i = 0; i < count; ++i) {
sum += array[i] && 1;
}
} else {
for (let i = 0; i < viewEnd; ++i) {
const v = view[i];
sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
}
for (let i = backStart; i < count; ++i) {
sum += array[i] && 1;
}
}
return sum / count;
}

View File

@@ -677,6 +677,8 @@ export namespace Mesh {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('mesh'),
aPosition: mesh.vertexBuffer,
aNormal: mesh.normalBuffer,
aGroup: mesh.groupBuffer,

View File

@@ -182,6 +182,8 @@ export namespace Points {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('points'),
aPosition: points.centerBuffer,
aGroup: points.groupBuffer,
boundingSphere: ValueCell.create(boundingSphere),

View File

@@ -11,7 +11,7 @@ import { LocationIterator } from '../util/location-iterator';
import { Location, NullLocation } from '../../mol-model/location';
import { SizeTheme } from '../../mol-theme/size';
import { Geometry } from './geometry';
import { decodeFloatRGB, encodeFloatRGBtoArray } from '../../mol-util/float-packing';
import { unpackRGBToInt, packIntToRGBArray } from '../../mol-util/number-packing';
export type SizeType = 'uniform' | 'instance' | 'group' | 'groupInstance'
@@ -44,7 +44,7 @@ export function getMaxSize(sizeData: SizeData): number {
let maxSize = 0;
const array = sizeData.tSize.ref.value.array;
for (let i = 0, il = array.length; i < il; i += 3) {
const value = decodeFloatRGB(array[i], array[i + 1], array[i + 2]);
const value = unpackRGBToInt(array[i], array[i + 1], array[i + 2]);
if (maxSize < value) maxSize = value;
}
return maxSize / sizeDataFactor;
@@ -103,7 +103,7 @@ export function createInstanceSize(locationIt: LocationIterator, sizeFn: Locatio
locationIt.reset();
while (locationIt.hasNext && !locationIt.isNextNewInstance) {
const v = locationIt.move();
encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.instanceIndex * 3);
packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.instanceIndex * 3);
locationIt.skipInstance();
}
return createTextureSize(sizes, 'instance', sizeData);
@@ -116,7 +116,7 @@ export function createGroupSize(locationIt: LocationIterator, sizeFn: LocationSi
locationIt.reset();
while (locationIt.hasNext && !locationIt.isNextNewInstance) {
const v = locationIt.move();
encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.groupIndex * 3);
packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.groupIndex * 3);
}
return createTextureSize(sizes, 'group', sizeData);
}
@@ -129,7 +129,7 @@ export function createGroupInstanceSize(locationIt: LocationIterator, sizeFn: Lo
locationIt.reset();
while (locationIt.hasNext) {
const v = locationIt.move();
encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.index * 3);
packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.index * 3);
}
return createTextureSize(sizes, 'groupInstance', sizeData);
}

View File

@@ -183,6 +183,8 @@ export namespace Spheres {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('spheres'),
aPosition: spheres.centerBuffer,
aMapping: spheres.mappingBuffer,
aGroup: spheres.groupBuffer,

View File

@@ -224,6 +224,8 @@ export namespace Text {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('text'),
aPosition: text.centerBuffer,
aMapping: text.mappingBuffer,
aDepth: text.depthBuffer,

View File

@@ -147,6 +147,8 @@ export namespace TextureMesh {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
dGeometryType: ValueCell.create('textureMesh'),
uGeoTexDim: textureMesh.geoTextureDim,
tPosition: textureMesh.vertexTexture,
tGroup: textureMesh.groupTexture,
@@ -172,7 +174,6 @@ export namespace TextureMesh {
dXrayShaded: ValueCell.create(props.xrayShaded),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
dGeoTexture: ValueCell.create(true),
meta: ValueCell.create(textureMesh.meta),
};

View File

@@ -11,7 +11,7 @@ import { Values, TextureSpec } from '../../renderable/schema';
import { Texture } from '../../../mol-gl/webgl/texture';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { ValueCell } from '../../../mol-util';
import { decodeFloatRGB } from '../../../mol-util/float-packing';
import { unpackRGBToInt } from '../../../mol-util/number-packing';
import { QuadSchema, QuadValues } from '../util';
import { quad_vert } from '../../../mol-gl/shader/quad.vert';
import { sum_frag } from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
@@ -96,5 +96,5 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
return isWebGL2(gl)
? sumInts[0]
: decodeFloatRGB(sumBytes[0], sumBytes[1], sumBytes[2]);
: unpackRGBToInt(sumBytes[0], sumBytes[1], sumBytes[2]);
}

View File

@@ -39,13 +39,14 @@ const IsosurfaceSchema = {
uGridTransform: UniformSpec('m4'),
uScale: UniformSpec('v2'),
dPackedGroup: DefineSpec('boolean')
dPackedGroup: DefineSpec('boolean'),
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
};
type IsosurfaceValues = Values<typeof IsosurfaceSchema>
const IsosurfaceName = 'isosurface';
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3): ComputeRenderable<IsosurfaceValues> {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
@@ -65,15 +66,16 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
ValueCell.update(v.uScale, scale);
ValueCell.update(v.dPackedGroup, packedGroup);
ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join(''));
ctx.namedComputeRenderables[IsosurfaceName].update();
} else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean) {
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3) {
// console.log('uSize', Math.pow(2, levels))
const values: IsosurfaceValues = {
...QuadValues,
@@ -94,7 +96,8 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
uGridTransform: ValueCell.create(transform),
uScale: ValueCell.create(scale),
dPackedGroup: ValueCell.create(packedGroup)
dPackedGroup: ValueCell.create(packedGroup),
dAxisOrder: ValueCell.create(axisOrder.join('')),
};
const schema = { ...IsosurfaceSchema };
@@ -115,7 +118,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.clearColor(0, 0, 0, 0);
}
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) {
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
@@ -173,7 +176,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
groupTexture.attachFramebuffer(framebuffer, 1);
normalTexture.attachFramebuffer(framebuffer, 2);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
ctx.state.currentRenderItemId = -1;
framebuffer.bind();
@@ -204,7 +207,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
*
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
*/
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
// console.time('calcActiveVoxels');
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
// ctx.waitForGpuCommandsCompleteSync();
@@ -216,7 +219,7 @@ export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDi
// console.timeEnd('createHistogramPyramid');
// console.time('createIsosurfaceBuffers');
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, vertexTexture, groupTexture, normalTexture);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, vertexTexture, groupTexture, normalTexture);
// ctx.waitForGpuCommandsCompleteSync();
// console.timeEnd('createIsosurfaceBuffers');

View File

@@ -17,12 +17,6 @@ export const DirectVolumeSchema = {
aPosition: AttributeSpec('float32', 3, 0),
elements: ElementsSpec('uint32'),
uColor: UniformSpec('v3'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
uIsoValue: UniformSpec('v2'),
uBboxMin: UniformSpec('v3'),
uBboxMax: UniformSpec('v3'),
uBboxSize: UniformSpec('v3'),
@@ -31,9 +25,7 @@ export const DirectVolumeSchema = {
uJumpLength: UniformSpec('f'),
uTransform: UniformSpec('m4'),
uGridDim: UniformSpec('v3'),
dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
dSingleLayer: DefineSpec('boolean'),
tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'),
tTransferTex: TextureSpec('image-uint8', 'alpha', 'ubyte', 'linear'),
uTransferScale: UniformSpec('f'),
dGridTexType: DefineSpec('string', ['2d', '3d']),
@@ -45,10 +37,8 @@ export const DirectVolumeSchema = {
uCartnToUnit: UniformSpec('m4'),
uUnitToCartn: UniformSpec('m4'),
dPackedGroup: DefineSpec('boolean'),
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
uDoubleSided: UniformSpec('b'),
dFlipSided: DefineSpec('boolean'),
dFlatShaded: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
};

View File

@@ -182,7 +182,7 @@ export const ColorSchema = {
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tColorGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance', 'volume', 'volumeInstance']),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance', 'volume', 'volumeInstance', 'direct']),
dUsePalette: DefineSpec('boolean'),
} as const;
export type ColorSchema = typeof ColorSchema
@@ -258,6 +258,8 @@ export type ClippingSchema = typeof ClippingSchema
export type ClippingValues = Values<ClippingSchema>
export const BaseSchema = {
dGeometryType: DefineSpec('string', ['cylinders', 'directVolume', 'image', 'lines', 'mesh', 'points', 'spheres', 'text', 'textureMesh']),
...ColorSchema,
...MarkerSchema,
...OverpaintSchema,

View File

@@ -23,7 +23,6 @@ export const TextureMeshSchema = {
dFlipSided: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dGeoTexture: DefineSpec('boolean'),
uBumpFrequency: UniformSpec('f'),
uBumpAmplitude: UniformSpec('f'),
meta: ValueSpec('unknown')

View File

@@ -89,8 +89,8 @@ export const RendererParams = {
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
highlightStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
selectStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
highlightStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
selectStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
markerPriority: PD.Select(1, [[1, 'Highlight'], [2, 'Select']]),
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
@@ -136,7 +136,7 @@ function getLight(props: RendererProps['light'], light?: Light): Light {
namespace Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats, extensions: { fragDepth } } = ctx;
const { gl, state, stats } = ctx;
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
const light = getLight(p.light);
@@ -245,9 +245,9 @@ namespace Renderer {
globalUniformsNeedUpdate = false;
}
if (r.values.dRenderMode) { // indicates direct-volume
if ((variant === 'pick' || variant === 'depth') && r.values.dRenderMode.ref.value === 'volume') {
return; // no picking/depth in volume mode
if (r.values.dGeometryType.ref.value === 'directVolume') {
if (variant !== 'colorWboit' && variant !== 'colorBlended') {
return; // only color supported
}
// culling done in fragment shader
@@ -256,14 +256,8 @@ namespace Renderer {
if (variant === 'colorBlended') {
// depth test done manually in shader against `depthTexture`
// still need to enable when fragDepth can be used to write depth
if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) {
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
} else {
state.enable(gl.DEPTH_TEST);
state.depthMask(r.values.uAlpha.ref.value === 1.0);
}
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
}
} else {
if (r.values.uDoubleSided) {
@@ -506,7 +500,7 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit');
}
}
@@ -522,7 +516,7 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit');
}
}

View File

@@ -198,7 +198,7 @@ export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { drawBuf
import { directVolume_vert } from './shader/direct-volume.vert';
import { directVolume_frag } from './shader/direct-volume.frag';
export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_vert, directVolume_frag, { fragDepth: 'optional', drawBuffers: 'optional' });
export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_vert, directVolume_frag, { fragDepth: 'optional', drawBuffers: 'optional' }, {}, ignoreDefine);
import { image_vert } from './shader/image.vert';
import { image_frag } from './shader/image.frag';

View File

@@ -57,11 +57,11 @@ export const assign_color_varying = `
#endif
#elif defined(dRenderVariant_pick)
if (uPickType == 1) {
vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
vColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
} else if (uPickType == 2) {
vColor = vec4(encodeFloatRGB(aInstance), 1.0);
vColor = vec4(packIntToRGB(aInstance), 1.0);
} else {
vColor = vec4(encodeFloatRGB(group), 1.0);
vColor = vec4(packIntToRGB(group), 1.0);
}
#endif

View File

@@ -1,6 +1,6 @@
export const assign_group = `
#ifdef dGeoTexture
float group = decodeFloatRGB(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
#ifdef dGeometryType_textureMesh
float group = unpackRGBToInt(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
#else
float group = aGroup;
#endif

View File

@@ -1,7 +1,7 @@
export const assign_position = `
mat4 model = uModel * aTransform;
mat4 modelView = uView * model;
#ifdef dGeoTexture
#ifdef dGeometryType_textureMesh
vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
#else
vec3 position = aPosition;

View File

@@ -4,11 +4,11 @@ export const assign_size = `
#elif defined(dSizeType_attribute)
float size = aSize;
#elif defined(dSizeType_instance)
float size = decodeFloatRGB(readFromTexture(tSize, aInstance, uSizeTexDim).rgb);
float size = unpackRGBToInt(readFromTexture(tSize, aInstance, uSizeTexDim).rgb);
#elif defined(dSizeType_group)
float size = decodeFloatRGB(readFromTexture(tSize, group, uSizeTexDim).rgb);
float size = unpackRGBToInt(readFromTexture(tSize, group, uSizeTexDim).rgb);
#elif defined(dSizeType_groupInstance)
float size = decodeFloatRGB(readFromTexture(tSize, aInstance * float(uGroupCount) + group, uSizeTexDim).rgb);
float size = unpackRGBToInt(readFromTexture(tSize, aInstance * float(uGroupCount) + group, uSizeTexDim).rgb);
#endif
#if defined(dSizeType_instance) || defined(dSizeType_group) || defined(dSizeType_groupInstance)

View File

@@ -25,6 +25,10 @@ export const common = `
#define saturate(a) clamp(a, 0.0, 1.0)
#if __VERSION__ == 100
#define round(x) floor((x) + 0.5)
#endif
float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
vec2 ivec2Div(const in vec2 a, const in vec2 b) { return vec2(ivec2(a) / ivec2(b)); }
float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
@@ -32,13 +36,8 @@ int imod(const in int a, const in int b) { return a - b * (a / b); }
float pow2(const in float x) { return x * x; }
const float maxFloat = 10000.0; // NOTE constant also set in TypeScript
const float floatLogFactor = 9.210440366976517; // log(maxFloat + 1.0);
float encodeFloatLog(const in float value) { return log(value + 1.0) / floatLogFactor; }
float decodeFloatLog(const in float value) { return exp(value * floatLogFactor) - 1.0; }
vec3 encodeFloatRGB(in float value) {
value = clamp(value, 0.0, 16777216.0 - 1.0) + 1.0;
vec3 packIntToRGB(in float value) {
value = clamp(round(value), 0.0, 16777216.0 - 1.0) + 1.0;
vec3 c = vec3(0.0);
c.b = mod(value, 256.0);
value = floor(value / 256.0);
@@ -47,8 +46,8 @@ vec3 encodeFloatRGB(in float value) {
c.r = mod(value, 256.0);
return c / 255.0;
}
float decodeFloatRGB(const in vec3 rgb) {
return (rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0) - 1.0;
float unpackRGBToInt(const in vec3 rgb) {
return (floor(rgb.r * 255.0 + 0.5) * 256.0 * 256.0 + floor(rgb.g * 255.0 + 0.5) * 256.0 + floor(rgb.b * 255.0 + 0.5)) - 1.0;
}
vec2 packUnitIntervalToRG(const in float v) {

View File

@@ -18,7 +18,7 @@ export const wboit_write = `
float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0);
gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);
// extra alpha is to handle pre-multiplied alpha
#if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface)
#ifndef dGeometryType_directVolume
gl_FragData[1] = vec4((uTransparentBackground ? alpha : 1.0) * alpha * wboitWeight);
#else
gl_FragData[1] = vec4(alpha * alpha * wboitWeight);

View File

@@ -35,7 +35,7 @@ uniform float uResolution;
void main() {
vec3 position = readFromTexture(tPosition, SampleID, uGeoTexDim).xyz;
float group = decodeFloatRGB(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
float group = unpackRGBToInt(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
position = (aTransform * vec4(position, 1.0)).xyz;
gl_PointSize = 7.0;

View File

@@ -38,7 +38,6 @@ varying vec4 vBoundingSphere;
varying mat4 vTransform;
uniform mat4 uInvView;
uniform vec2 uIsoValue;
uniform vec3 uGridDim;
uniform vec3 uBboxSize;
uniform sampler2D tTransferTex;
@@ -76,11 +75,9 @@ uniform float uXrayEdgeFalloff;
uniform float uInteriorDarkening;
uniform bool uInteriorColorFlag;
uniform vec3 uInteriorColor;
bool interior;
uniform bool uRenderWboit;
uniform bool uDoubleSided;
uniform int uPickType;
uniform float uNear;
uniform float uFar;
@@ -104,27 +101,22 @@ uniform mat4 uCartnToUnit;
uniform sampler3D tGridTex;
#endif
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
uniform vec3 uColor;
#elif defined(dColorType_texture)
uniform vec2 uColorTexDim;
uniform sampler2D tColor;
#endif
#if defined(dColorType_uniform)
uniform vec3 uColor;
#elif defined(dColorType_texture)
uniform vec2 uColorTexDim;
uniform sampler2D tColor;
#endif
#ifdef dOverpaint
#if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#endif
#ifdef dOverpaint
#if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
uniform vec2 uSubstanceTexDim;
uniform sampler2D tSubstance;
#endif
#endif
#ifdef dUsePalette
uniform sampler2D tPalette;
#endif
#if defined(dGridTexType_2d)
@@ -148,8 +140,8 @@ float calcDepth(const in vec3 pos) {
return 0.5 + 0.5 * clipZW.x / clipZW.y;
}
vec4 transferFunction(float value) {
return texture2D(tTransferTex, vec2(value, 0.0));
float transferFunction(float value) {
return texture2D(tTransferTex, vec2(value, 0.0)).a;
}
float getDepth(const in vec2 coords) {
@@ -174,7 +166,7 @@ vec3 v3m4(vec3 p, mat4 m) {
float preFogAlphaBlended = 0.0;
vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#if defined(dRenderVariant_color) && !defined(dIgnoreLight)
#if !defined(dIgnoreLight)
mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
#endif
mat4 cartnToUnit = uCartnToUnit * inverse4(vTransform);
@@ -190,21 +182,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
float value = 0.0;
vec4 src = vec4(0.0);
vec4 dst = vec4(0.0);
bool hit = false;
float fragmentDepth;
vec3 posMin = vec3(0.0);
vec3 posMax = vec3(1.0) - vec3(1.0) / uGridDim;
vec3 unitPos;
vec3 isoPos;
vec3 nextPos;
float nextValue;
vec3 color = vec3(0.45, 0.55, 0.8);
vec4 overpaint = vec4(0.0);
vec3 substance = vec3(0.0);
vec4 material;
vec4 overpaint;
float metalness = uMetalness;
float roughness = uRoughness;
@@ -227,7 +216,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
if (unitPos.x > posMax.x || unitPos.y > posMax.y || unitPos.z > posMax.z ||
unitPos.x < posMin.x || unitPos.y < posMin.y || unitPos.z < posMin.z
) {
if (hit) break;
prevValue = value;
pos += step;
continue;
@@ -246,226 +234,108 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
}
}
#if defined(dRenderMode_isosurface)
if (prevValue > 0.0 && ( // there was a prev Value
(prevValue < uIsoValue.x && value > uIsoValue.x) || // entering isosurface
(prevValue > uIsoValue.x && value < uIsoValue.x) // leaving isosurface
)) {
isoPos = v3m4(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)))), cartnToUnit);
vec4 mvPosition = modelViewTransform * vec4(unitPos * uGridDim, 1.0);
if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
break;
vec4 mvPosition = modelViewTransform * vec4(isoPos * uGridDim, 1.0);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
vec3 vModelPosition = v3m4(isoPos * uGridDim, modelTransform);
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
prevValue = value;
pos += step;
continue;
}
#endif
float depth = calcDepth(mvPosition.xyz);
if (depth > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
break;
#ifdef enabledFragDepth
if (!hit) {
gl_FragDepthEXT = depth;
}
#endif
#if defined(dRenderVariant_pick)
if (uPickType == 1) {
return vec4(encodeFloatRGB(float(uObjectId)), 1.0);
} else if (uPickType == 2) {
return vec4(encodeFloatRGB(vInstance), 1.0);
} else {
#ifdef dPackedGroup
return vec4(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb, 1.0);
#else
vec3 g = floor(isoPos * uGridDim + 0.5);
return vec4(encodeFloatRGB(g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y), 1.0);
#endif
}
#elif defined(dRenderVariant_depth)
#ifdef enabledFragDepth
return packDepthToRGBA(gl_FragDepthEXT);
#else
return packDepthToRGBA(depth);
#endif
#elif defined(dRenderVariant_color)
#ifdef dPackedGroup
float group = decodeFloatRGB(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb);
#else
vec3 g = floor(isoPos * uGridDim + 0.5);
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
#endif
#if defined(dColorType_uniform)
color = uColor;
#elif defined(dColorType_instance)
color = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
#elif defined(dColorType_group)
color = readFromTexture(tColor, group, uColorTexDim).rgb;
#elif defined(dColorType_groupInstance)
color = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
#elif defined(dColorType_vertex)
color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
#elif defined(dColorType_vertexInstance)
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));
#endif
color = mix(color, overpaint.rgb, overpaint.a);
#endif
// handle flipping and negative isosurfaces
#ifdef dFlipSided
bool flipped = value < uIsoValue.y; // flipped
#else
bool flipped = value > uIsoValue.y;
#endif
interior = value < uIsoValue.x && flipped;
if (uDoubleSided) {
if (interior) {
prevValue = value;
pos += step;
continue;
}
}
vec3 vViewPosition = mvPosition.xyz;
vec4 material = vec4(color, uAlpha);
#ifdef dIgnoreLight
gl_FragColor = material;
#else
#if defined(dFlatShaded)
// nearest grid point
isoPos = floor(isoPos * uGridDim + 0.5) / uGridDim;
#endif
#ifdef dPackedGroup
// compute gradient by central differences
gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a;
#else
gradient = textureVal(isoPos).xyz * 2.0 - 1.0;
#endif
vec3 normal = -normalize(normalMatrix * normalize(gradient));
normal = normal * (float(flipped) * 2.0 - 1.0);
normal = normal * -(float(interior) * 2.0 - 1.0);
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance)
substance = readFromTexture(tSubstance, vInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_vertexInstance)
substance = texture3dFrom1dTrilinear(tSubstance, isoPos, uGridDim, uSubstanceTexDim, vInstance * float(uVertexCount)).rgb;
#endif
metalness = mix(metalness, substance.r, substance.b);
roughness = mix(roughness, substance.g, substance.b);
#endif
#include apply_light_color
#endif
float marker = uMarker;
if (uMarker == -1.0) {
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
#include apply_interior_color
#include apply_marker_color
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
fragmentDepth = depth;
#include apply_fog
src = gl_FragColor;
if (!uTransparentBackground) {
// done in 'apply_fog' otherwise
src.rgb *= src.a;
}
dst = (1.0 - dst.a) * src + dst; // standard blending
#endif
#ifdef dSingleLayer
break;
#endif
hit = true;
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
prevValue = value;
pos += step;
continue;
}
prevValue = value;
#elif defined(dRenderMode_volume)
vec4 mvPosition = modelViewTransform * vec4(unitPos * uGridDim, 1.0);
if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
break;
#endif
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
prevValue = value;
pos += step;
continue;
}
#endif
vec3 vViewPosition = mvPosition.xyz;
material.a = transferFunction(value);
#if defined(dRenderVariant_color)
vec3 vViewPosition = mvPosition.xyz;
vec4 material = transferFunction(value);
#ifdef dIgnoreLight
gl_FragColor.rgb = material.rgb;
#else
if (material.a >= 0.01) {
#ifdef dPackedGroup
// compute gradient by central differences
gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
#else
gradient = cell.xyz * 2.0 - 1.0;
#endif
vec3 normal = -normalize(normalMatrix * normalize(gradient));
#include apply_light_color
} else {
gl_FragColor.rgb = material.rgb;
}
#endif
gl_FragColor.a = material.a * uAlpha * uTransferScale;
float marker = uMarker;
if (uMarker == -1.0) {
#ifdef dPackedGroup
float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
#else
vec3 g = floor(unitPos * uGridDim + 0.5);
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
#endif
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
#include apply_marker_color
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
fragmentDepth = calcDepth(mvPosition.xyz);
#include apply_fog
src = gl_FragColor;
if (!uTransparentBackground) {
// done in 'apply_fog' otherwise
src.rgb *= src.a;
}
dst = (1.0 - dst.a) * src + dst; // standard blending
#ifdef dPackedGroup
float group = unpackRGBToInt(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
#else
vec3 g = floor(unitPos * uGridDim + 0.5);
// note that we swap x and z because the texture is flipped around y
#if defined(dAxisOrder_012)
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y; // 210
#elif defined(dAxisOrder_021)
float group = g.y + g.z * uGridDim.y + g.x * uGridDim.y * uGridDim.z; // 120
#elif defined(dAxisOrder_102)
float group = g.z + g.x * uGridDim.z + g.y * uGridDim.z * uGridDim.x; // 201
#elif defined(dAxisOrder_120)
float group = g.x + g.z * uGridDim.x + g.y * uGridDim.x * uGridDim.z; // 021
#elif defined(dAxisOrder_201)
float group = g.y + g.x * uGridDim.y + g.z * uGridDim.y * uGridDim.x; // 102
#elif defined(dAxisOrder_210)
float group = g.x + g.y * uGridDim.x + g.z * uGridDim.x * uGridDim.y; // 012
#endif
#endif
#if defined(dColorType_direct) && defined(dUsePalette)
material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
#elif defined(dColorType_uniform)
material.rgb = uColor;
#elif defined(dColorType_instance)
material.rgb = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
#elif defined(dColorType_group)
material.rgb = readFromTexture(tColor, group, uColorTexDim).rgb;
#elif defined(dColorType_groupInstance)
material.rgb = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
#elif defined(dColorType_vertex)
material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
#elif defined(dColorType_vertexInstance)
material.rgb = 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));
#endif
material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
#endif
#ifdef dIgnoreLight
gl_FragColor.rgb = material.rgb;
#else
if (material.a >= 0.01) {
#ifdef dPackedGroup
// compute gradient by central differences
gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
#else
gradient = cell.xyz * 2.0 - 1.0;
#endif
vec3 normal = -normalize(normalMatrix * normalize(gradient));
#include apply_light_color
} else {
gl_FragColor.rgb = material.rgb;
}
#endif
gl_FragColor.a = material.a * uAlpha * uTransferScale;
float marker = uMarker;
if (uMarker == -1.0) {
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
#include apply_marker_color
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
fragmentDepth = calcDepth(mvPosition.xyz);
#include apply_fog
src = gl_FragColor;
if (!uTransparentBackground) {
// done in 'apply_fog' otherwise
src.rgb *= src.a;
}
dst = (1.0 - dst.a) * src + dst; // standard blending
// break if the color is opaque enough
if (dst.a > 0.95)
break;
@@ -473,12 +343,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
pos += step;
}
#if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
// ensure depth is written everywhere
if (!hit)
gl_FragDepthEXT = 1.0;
#endif
return dst;
}
@@ -489,21 +353,6 @@ void main() {
if (gl_FrontFacing)
discard;
#ifdef dRenderVariant_marking
// not supported
discard;
#endif
#if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
#if defined(dRenderMode_volume)
// always ignore pick & depth for volume
discard;
#elif defined(dRenderMode_isosurface)
if (uAlpha < uPickingAlphaThreshold)
discard; // ignore so the element below can be picked
#endif
#endif
vec3 rayDir = mix(normalize(vOrigPos - uCameraPosition), uCameraDir, uIsOrtho);
vec3 step = rayDir * uStepScale;
@@ -512,21 +361,9 @@ void main() {
vec3 start = mix(uCameraPosition, vOrigPos, uIsOrtho) + (d * rayDir);
gl_FragColor = raymarch(start, step, rayDir);
#if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
// discard when nothing was hit
if (gl_FragColor == vec4(0.0))
discard;
#endif
#if defined(dRenderVariant_color)
#if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
float fragmentDepth = gl_FragDepthEXT;
#else
float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
#endif
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
interior = false;
#include wboit_write
#endif
float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
bool interior = false;
#include wboit_write
}
`;

View File

@@ -51,7 +51,7 @@ void main() {
#endif
if (dist * uRadiusFactorInv > minDistance + uResolution * 0.05)
discard;
gl_FragColor.rgb = encodeFloatRGB(vGroup);
gl_FragColor.rgb = packIntToRGB(vGroup);
#endif
}
`;

View File

@@ -33,12 +33,12 @@ void main(void) {
c = texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0;
d = texture2D(tInputLevel, position + vec2(k, k)).r * 255.0;
} else {
a = decodeFloatRGB(texture2D(tPreviousLevel, position).rgb);
b = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
c = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
d = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
a = unpackRGBToInt(texture2D(tPreviousLevel, position).rgb);
b = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
c = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
d = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
}
gl_FragColor = vec4(encodeFloatRGB(a + b + c + d), 1.0);
gl_FragColor = vec4(packIntToRGB(a + b + c + d), 1.0);
#else
int a, b, c, d;

View File

@@ -105,9 +105,9 @@ void main() {
if (imageData.a < 0.3)
discard;
if (uPickType == 1) {
gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
} else if (uPickType == 2) {
gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0);
gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
} else {
gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
}
@@ -118,7 +118,7 @@ void main() {
#elif defined(dRenderVariant_marking)
float marker = uMarker;
if (uMarker == -1.0) {
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
@@ -144,7 +144,7 @@ void main() {
float marker = uMarker;
if (uMarker == -1.0) {
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}

View File

@@ -74,7 +74,7 @@ int idot4(const in ivec4 a, const in ivec4 b) {
#if __VERSION__ == 100
int pyramidVoxel(vec2 pos) {
return int(decodeFloatRGB(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
return int(unpackRGBToInt(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
}
#else
int pyramidVoxel(vec2 pos) {
@@ -86,6 +86,25 @@ vec4 baseVoxel(vec2 pos) {
return texture2D(tActiveVoxelsBase, pos / uSize);
}
vec4 getGroup(const in vec3 p) {
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
// note that we swap x and z because the texture is flipped around y
#if defined(dAxisOrder_012)
float group = p.z + p.y * gridDim.z + p.x * gridDim.z * gridDim.y; // 210
#elif defined(dAxisOrder_021)
float group = p.y + p.z * gridDim.y + p.x * gridDim.y * gridDim.z; // 120
#elif defined(dAxisOrder_102)
float group = p.z + p.x * gridDim.z + p.y * gridDim.z * gridDim.x; // 201
#elif defined(dAxisOrder_120)
float group = p.x + p.z * gridDim.x + p.y * gridDim.x * gridDim.z; // 021
#elif defined(dAxisOrder_201)
float group = p.y + p.x * gridDim.y + p.z * gridDim.y * gridDim.x; // 102
#elif defined(dAxisOrder_210)
float group = p.x + p.y * gridDim.x + p.z * gridDim.x * gridDim.y; // 012
#endif
return vec4(group > 16777215.5 ? vec3(1.0) : packIntToRGB(group), 1.0);
}
void main(void) {
// get 1D index
int vI = int(gl_FragCoord.x) + int(gl_FragCoord.y) * int(uSize);
@@ -255,18 +274,13 @@ void main(void) {
#ifdef dPackedGroup
gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0);
#else
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
float group = coord3d.z + coord3d.y * gridDim.z + coord3d.x * gridDim.z * gridDim.y;
gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
gl_FragData[1] = getGroup(coord3d);
#endif
#else
#ifdef dPackedGroup
gl_FragData[1] = vec4(t < 0.5 ? d0.rgb : d1.rgb, 1.0);
#else
vec3 b = t < 0.5 ? b0 : b1;
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
float group = b.z + b.y * gridDim.z + b.x * gridDim.z * gridDim.y;
gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
gl_FragData[1] = getGroup(t < 0.5 ? b0 : b1);
#endif
#endif

View File

@@ -16,7 +16,7 @@ precision highp sampler2D;
#include common_clip
#include texture3d_from_2d_linear
#ifdef dGeoTexture
#ifdef dGeometryType_textureMesh
uniform vec2 uGeoTexDim;
uniform sampler2D tPosition;
uniform sampler2D tGroup;
@@ -39,7 +39,7 @@ void main(){
#include assign_color_varying
#include clip_instance
#ifdef dGeoTexture
#ifdef dGeometryType_textureMesh
vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
#else
vec3 normal = aNormal;

View File

@@ -244,6 +244,84 @@ GASTEIGER
25 13 23 1
26 13 24 1`;
const Mol2StringCrysin = `@<TRIPOS>MOLECULE
1144204
12 11 2 0 0
SMALL
USER_CHARGES
****
Generated from the CSD
@<TRIPOS>ATOM
1 Cl1 0.0925 3.6184 1.9845 Cl 1 RES1 -1.0000
2 C1 -4.7391 0.3350 0.4215 C.ar 2 RES2 0.0000
3 C2 -3.4121 0.2604 0.9351 C.ar 2 RES2 0.0000
4 C3 -2.9169 1.2555 1.7726 C.ar 2 RES2 0.0000
5 C4 -3.7118 2.3440 2.1099 C.ar 2 RES2 0.0000
6 C5 -5.0314 2.4052 1.6209 C.ar 2 RES2 0.0000
7 C6 -5.5372 1.4057 0.7962 C.ar 2 RES2 0.0000
8 C7 -6.9925 1.4547 0.3334 C.3 2 RES2 0.0000
9 C8 -7.8537 0.5554 1.1859 C.3 2 RES2 0.0000
10 N1 -9.3089 0.7134 0.8192 N.3 2 RES2 1.0000
11 O1 -2.6613 -0.8147 0.5707 O.3 2 RES2 0.0000
12 O2 -1.6204 1.0919 2.2584 O.3 2 RES2 0.0000
@<TRIPOS>BOND
1 2 3 ar
2 3 4 ar
3 4 5 ar
4 5 6 ar
5 6 7 ar
6 7 2 ar
7 8 7 1
8 9 8 1
9 10 9 1
10 11 3 1
11 12 4 1
@<TRIPOS>SUBSTRUCTURE
1 RES1 1 GROUP 0 **** **** 0
2 RES2 2 GROUP 0 **** **** 0
@<TRIPOS>CRYSIN
10.5150 11.1300 7.9380 90.0000 90.0000 90.0000 29 5
@<TRIPOS>MOLECULE
1144204
12 11 2 0 0
SMALL
USER_CHARGES
****
Generated from the CSD
@<TRIPOS>ATOM
1 Cl1 0.0925 3.6184 1.9845 Cl 1 RES1 -1.0000
2 C1 -4.7391 0.3350 0.4215 C.ar 2 RES2 0.0000
3 C2 -3.4121 0.2604 0.9351 C.ar 2 RES2 0.0000
4 C3 -2.9169 1.2555 1.7726 C.ar 2 RES2 0.0000
5 C4 -3.7118 2.3440 2.1099 C.ar 2 RES2 0.0000
6 C5 -5.0314 2.4052 1.6209 C.ar 2 RES2 0.0000
7 C6 -5.5372 1.4057 0.7962 C.ar 2 RES2 0.0000
8 C7 -6.9925 1.4547 0.3334 C.3 2 RES2 0.0000
9 C8 -7.8537 0.5554 1.1859 C.3 2 RES2 0.0000
10 N1 -9.3089 0.7134 0.8192 N.3 2 RES2 1.0000
11 O1 -2.6613 -0.8147 0.5707 O.3 2 RES2 0.0000
12 O2 -1.6204 1.0919 2.2584 O.3 2 RES2 0.0000
@<TRIPOS>BOND
1 2 3 ar
2 3 4 ar
3 4 5 ar
4 5 6 ar
5 6 7 ar
6 7 2 ar
7 8 7 1
8 9 8 1
9 10 9 1
10 11 3 1
11 12 4 1
@<TRIPOS>SUBSTRUCTURE
1 RES1 1 GROUP 0 **** **** 0
2 RES2 2 GROUP 0 **** **** 0
@<TRIPOS>CRYSIN
10.5150 11.1300 7.9380 90.0000 90.0000 90.0000 29 5
`;
describe('mol2 reader', () => {
it('basic', async () => {
const parsed = await parseMol2(Mol2String, '').run();
@@ -397,4 +475,29 @@ describe('mol2 reader', () => {
// optional bond fields
expect(bonds.status_bits.value(0)).toBe('');
});
it('crysin', async () => {
const parsed = await parseMol2(Mol2StringCrysin, '').run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const mol2File = parsed.result;
// number of structures
expect(mol2File.structures.length).toBe(2);
// crysin fields
for (const data of mol2File.structures) {
expect(data.crysin).toEqual({
a: 10.5150,
b: 11.1300,
c: 7.9380,
alpha: 90.0,
beta: 90.0,
gamma: 90.0,
spaceGroup: 29,
setting: 5
});
}
});
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Zepei Xu <xuzepei19950617@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -259,6 +259,36 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> {
return ret;
}
function handleCrysin(state: State) {
const { tokenizer } = state;
while (tokenizer.position < tokenizer.data.length) {
const l = getTokenString(tokenizer);
if (l === '@<TRIPOS>MOLECULE') {
return;
} else if (l === '@<TRIPOS>CRYSIN') {
break;
} else {
markLine(tokenizer);
}
}
if (tokenizer.position >= tokenizer.data.length) return;
markLine(tokenizer);
const values = getTokenString(tokenizer).trim().split(reWhitespace);
return {
a: parseFloat(values[0]),
b: parseFloat(values[1]),
c: parseFloat(values[2]),
alpha: parseFloat(values[3]),
beta: parseFloat(values[4]),
gamma: parseFloat(values[5]),
spaceGroup: parseInt(values[6], 10),
setting: parseInt(values[7], 10),
};
}
async function parseInternal(ctx: RuntimeContext, data: string, name: string): Promise<Result<Schema.Mol2File>> {
const tokenizer = Tokenizer(data);
@@ -269,7 +299,8 @@ async function parseInternal(ctx: RuntimeContext, data: string, name: string): P
handleMolecule(state);
const atoms = await handleAtoms(state);
const bonds = await handleBonds(state);
structures.push({ molecule: state.molecule, atoms, bonds });
const crysin = handleCrysin(state);
structures.push({ molecule: state.molecule, atoms, bonds, crysin });
skipWhitespace(tokenizer);
while (getTokenString(tokenizer) !== '@<TRIPOS>MOLECULE' && tokenizer.position < tokenizer.data.length) {
markLine(tokenizer);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,7 @@ import { Column } from '../../../mol-data/db';
// @<TRIPOS>MOLECULE
// @<TRIPOS>ATOM
// @<TRIPOS>BOND
// @<TRIPOS>CRYSIN
//
// note that the format is not a fixed column format but white space separated
@@ -56,10 +57,22 @@ export interface Mol2Bonds {
status_bits: Column<string>
}
export interface Mol2Crysin {
a: number
b: number
c: number
alpha: number
beta: number
gamma: number
spaceGroup: number
setting: number
}
export interface Mol2Structure {
molecule: Readonly<Mol2Molecule>,
atoms: Readonly<Mol2Atoms>,
bonds: Readonly<Mol2Bonds>
crysin?: Readonly<Mol2Crysin>
}
export interface Mol2File {

View File

@@ -14,7 +14,7 @@ import { ValueCell } from '../../../mol-util';
import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
import { decodeFloatRGB } from '../../../mol-util/float-packing';
import { unpackRGBToInt } from '../../../mol-util/number-packing';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values } from '../../../mol-gl/renderable/schema';
@@ -462,7 +462,7 @@ function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texD
for (let ix = 0; ix < dx; ++ix) {
const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix);
data[j] = image[idx + 3] / 255;
idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2]);
idData[j] = unpackRGBToInt(image[idx], image[idx + 1], image[idx + 2]);
j++;
}
}

View File

@@ -1,11 +1,11 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column, Table } from '../../mol-data/db';
import { Model } from '../../mol-model/structure/model';
import { Model, Symmetry } from '../../mol-model/structure/model';
import { BondType, MoleculeType } from '../../mol-model/structure/model/types';
import { RuntimeContext, Task } from '../../mol-task';
import { createModels } from './basic/parser';
@@ -14,22 +14,34 @@ import { ComponentBuilder } from './common/component';
import { EntityBuilder } from './common/entity';
import { ModelFormat } from '../format';
import { IndexPairBonds } from './property/bonds/index-pair';
import { Mol2File } from '../../mol-io/reader/mol2/schema';
import { Mol2Crysin, Mol2File } from '../../mol-io/reader/mol2/schema';
import { AtomPartialCharge } from './property/partial-charge';
import { Trajectory, ArrayTrajectory } from '../../mol-model/structure';
import { guessElementSymbolString } from './util';
import { ModelSymmetry } from './property/symmetry';
import { Spacegroup, SpacegroupCell } from '../../mol-math/geometry';
import { Vec3 } from '../../mol-math/linear-algebra';
async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
const models: Model[] = [];
for (let i = 0, il = mol2.structures.length; i < il; ++i) {
const { atoms, bonds, molecule } = mol2.structures[i];
const { molecule, atoms, bonds, crysin } = mol2.structures[i];
const A = Column.ofConst('A', atoms.count, Column.Schema.str);
const type_symbol = new Array<string>(atoms.count);
let hasAtomType = false;
for (let i = 0; i < atoms.count; ++i) {
type_symbol[i] = guessElementSymbolString(atoms.atom_name.value(i));
if (atoms.atom_type.value(i).includes('.')) {
hasAtomType = true;
break;
}
}
for (let i = 0; i < atoms.count; ++i) {
type_symbol[i] = hasAtomType
? atoms.atom_type.value(i).split('.')[0].toUpperCase()
: guessElementSymbolString(atoms.atom_name.value(i));
}
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
@@ -74,6 +86,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
if (_models.frameCount > 0) {
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
const key = bonds.bond_id;
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
switch (x) {
case 'ar': // aromatic
@@ -100,7 +113,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
return BondType.Flag.Covalent;
}
}, Int8Array));
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order, flag }, count: atoms.count });
const pairBonds = IndexPairBonds.fromData({ pairs: { key, indexA, indexB, order, flag }, count: atoms.count });
const first = _models.representative;
IndexPairBonds.Provider.set(first, pairBonds);
@@ -110,6 +123,11 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
type: molecule.charge_type
});
if (crysin) {
const symmetry = getSymmetry(crysin);
if (symmetry) ModelSymmetry.Provider.set(first, symmetry);
}
models.push(first);
}
}
@@ -117,6 +135,24 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
return new ArrayTrajectory(models);
}
function getSymmetry(crysin: Mol2Crysin): Symmetry | undefined {
// TODO handle `crysin.setting`
if (crysin.setting !== 1) return;
const spaceCell = SpacegroupCell.create(
crysin.spaceGroup,
Vec3.create(crysin.a, crysin.b, crysin.c),
Vec3.scale(Vec3(), Vec3.create(crysin.alpha, crysin.beta, crysin.gamma), Math.PI / 180)
);
return {
spacegroup: Spacegroup.create(spaceCell),
assemblies: [],
isNonStandardCrystalFrame: false,
ncsOperators: []
};
}
//
export { Mol2Format };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,9 +10,9 @@ import { Column } from '../../../../mol-data/db';
import { FormatPropertyProvider } from '../../common/property';
import { BondType } from '../../../../mol-model/structure/model/types';
import { ElementIndex } from '../../../../mol-model/structure';
import { DefaultBondMaxRadius } from '../../../../mol-model/structure/structure/unit/bonds/common';
export type IndexPairsProps = {
readonly key: ArrayLike<number>
readonly order: ArrayLike<number>
readonly distance: ArrayLike<number>
readonly flag: ArrayLike<BondType.Flag>
@@ -22,17 +22,19 @@ export type IndexPairBonds = { bonds: IndexPairs, maxDistance: number }
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairsProps>, count: number): IndexPairs {
const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB);
const key = new Int32Array(builder.slotCount);
const order = new Int8Array(builder.slotCount);
const distance = new Array(builder.slotCount);
const flag = new Array(builder.slotCount);
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
builder.addNextEdge();
builder.assignProperty(key, props.key ? props.key[i] : -1);
builder.assignProperty(order, props.order ? props.order[i] : 1);
builder.assignProperty(distance, props.distance ? props.distance[i] : -1);
builder.assignProperty(flag, props.flag ? props.flag[i] : BondType.Flag.Covalent);
}
return builder.createGraph({ order, distance, flag });
return builder.createGraph({ key, order, distance, flag });
}
export namespace IndexPairBonds {
@@ -45,15 +47,33 @@ export namespace IndexPairBonds {
export type Data = {
pairs: {
indexA: Column<number>,
indexB: Column<number>
indexB: Column<number>,
key?: Column<number>,
order?: Column<number>,
/**
* Useful for bonds in periodic cells. That is, only bonds within the given
* distance are added. This allows for bond between periodic image but
* avoids unwanted bonds with wrong distances. If negative, test using the
* `maxDistance` option from `Props`.
*/
distance?: Column<number>,
flag?: Column<BondType.Flag>,
},
count: number
}
export const DefaultProps = { maxDistance: DefaultBondMaxRadius };
export const DefaultProps = {
/**
* If negative, test using element-based threshold, otherwise distance in Angstrom.
*
* This option exists to handle bonds in periodic cells. For systems that are
* made from beads (as opposed to atomic elements), set to a specific distance.
*
* Note that `Data` has a `distance` field which allows specifying a distance
* for each bond individually which takes precedence over this option.
*/
maxDistance: -1
};
export type Props = typeof DefaultProps
export function fromData(data: Data, props: Partial<Props> = {}): IndexPairBonds {
@@ -61,11 +81,12 @@ export namespace IndexPairBonds {
const { pairs, count } = data;
const indexA = pairs.indexA.toArray() as ArrayLike<ElementIndex>;
const indexB = pairs.indexB.toArray() as ArrayLike<ElementIndex>;
const key = pairs.key && pairs.key.toArray();
const order = pairs.order && pairs.order.toArray();
const distance = pairs.distance && pairs.distance.toArray();
const flag = pairs.flag && pairs.flag.toArray();
return {
bonds: getGraph(indexA, indexB, { order, distance, flag }, count),
bonds: getGraph(indexA, indexB, { key, order, distance, flag }, count),
maxDistance: p.maxDistance
};
}

View File

@@ -72,6 +72,7 @@ export namespace Stats {
}
} else if (size === 1) {
if (Unit.Traits.is(unit.traits, Unit.Trait.MultiChain)) {
// handled in `handleUnitChainsSimple`
return;
} else {
stats.elementCount += 1;
@@ -193,6 +194,12 @@ export namespace Stats {
if (stats.chainCount === 1) {
Location.set(stats.firstChainLoc, structure, unit, offsets[cI]);
}
} else if (size === 1) {
// need to handle here, skipped in `handleElement`
stats.elementCount += 1;
if (stats.elementCount === 1) {
Location.set(stats.firstElementLoc, structure, unit, eI);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 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>
@@ -110,6 +110,15 @@ export function getElementThreshold(i: number) {
return r;
}
export function getPairingThreshold(elementIndexA: number, elementIndexB: number, thresholdA: number, thresholdB: number) {
const thresholdAB = getElementPairThreshold(elementIndexA, elementIndexB);
return thresholdAB > 0
? thresholdAB
: elementIndexB < 0
? thresholdA
: (thresholdA + thresholdB) / 1.95; // not sure if avg or min but max is too big
}
const H_ID = __ElementIndex['H']!;
export function isHydrogen(i: number) {
return i === H_ID;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 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>
@@ -8,7 +8,7 @@
import { BondType, MoleculeType } from '../../../model/types';
import { Structure } from '../../structure';
import { Unit } from '../../unit';
import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps } from './common';
import { getElementIdx, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps, getPairingThreshold } from './common';
import { InterUnitBonds, InterUnitEdgeProps } from './data';
import { SortedArray } from '../../../../../mol-data/int';
import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
@@ -82,11 +82,31 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
if (type_symbolA.value(aI) === 'H' && type_symbolB.value(bI) === 'H') continue;
const aeI = getElementIdx(type_symbolA.value(aI));
const beI = getElementIdx(type_symbolA.value(bI));
const d = distance[i];
const dist = getDistance(unitA, aI, unitB, bI);
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
let add = false;
if (d >= 0) {
add = equalEps(dist, d, 0.3);
} else if (maxDistance >= 0) {
add = dist < maxDistance;
} else {
const pairingThreshold = getPairingThreshold(
aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
);
add = dist < pairingThreshold;
if (isHydrogen(aeI) && isHydrogen(beI)) {
// TODO handle molecular hydrogen
add = false;
}
}
if (add) {
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
}
}
@@ -155,13 +175,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue;
const thresholdAB = getElementPairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0
? thresholdAB
: beI < 0
? thresholdA
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
if (dist <= pairingThreshold) {
const atomIdB = label_atom_idB.value(bI);
const compIdB = label_comp_idB.value(residueIndexB[bI]);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 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>
@@ -9,7 +9,7 @@ import { BondType } from '../../../model/types';
import { IntraUnitBonds } from './data';
import { Unit } from '../../unit';
import { IntAdjacencyGraph } from '../../../../../mol-math/graph';
import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, DefaultBondComputationProps, getPairingThreshold } from './common';
import { SortedArray } from '../../../../../mol-data/int';
import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
import { StructureElement } from '../../element';
@@ -62,7 +62,8 @@ function findIndexPairBonds(unit: Unit.Atomic) {
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
const aI = atoms[_aI];
const isHa = type_symbol.value(aI) === 'H';
const aeI = getElementIdx(type_symbol.value(aI));
const isHa = isHydrogen(aeI);
const srcA = sourceIndex.value(aI);
@@ -72,11 +73,30 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const _bI = SortedArray.indexOf(unit.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
if (isHa && type_symbol.value(bI) === 'H') continue;
const beI = getElementIdx(type_symbol.value(bI));
const d = distance[i];
const dist = getDistance(unit, aI, bI);
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
let add = false;
if (d >= 0) {
add = equalEps(dist, d, 0.3);
} else if (maxDistance >= 0) {
add = dist < maxDistance;
} else {
const pairingThreshold = getPairingThreshold(
aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
);
add = dist < pairingThreshold;
if (isHa && isHydrogen(beI)) {
// TODO handle molecular hydrogen
add = false;
}
}
if (add) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;
orders[orders.length] = order[i];
@@ -214,13 +234,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue;
const thresholdAB = getElementPairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0
? thresholdAB
: beI < 0
? thresholdA
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
if (dist <= pairingThreshold) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;

View File

@@ -19,6 +19,7 @@ import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAn
import { Asset } from '../../mol-util/assets';
import { PluginConfig } from '../../mol-plugin/config';
import { getFileInfo } from '../../mol-util/file-info';
import { assertUnreachable } from '../../mol-util/type-helpers';
const DownloadModelRepresentationOptions = (plugin: PluginContext) => {
const representationDefault = plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
@@ -39,6 +40,7 @@ export const PdbDownloadProvider = {
'pdbe': PD.Group({
variant: PD.Select('updated-bcif', [['updated-bcif', 'Updated (bcif)'], ['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'updtaed-bcif' | 'archival', string][]),
}, { label: 'PDBe', isFlat: true }),
'pdbj': PD.EmptyGroup({ label: 'PDBj' }),
};
export type PdbDownloadProvider = keyof typeof PdbDownloadProvider;
@@ -104,15 +106,14 @@ const DownloadStructure = StateAction.build({
format = src.params.format;
break;
case 'pdb':
downloadParams = await (src.params.provider.server.name === 'pdbe'
? src.params.provider.server.params.variant === 'updated'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
: src.params.provider.server.params.variant === 'updated-bcif'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
: getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false)
: src.params.provider.server.params.encoding === 'cif'
? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id} (cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB: ${id} (bcif)`, true)
downloadParams = await (
src.params.provider.server.name === 'pdbe'
? getPdbeDownloadParams(src)
: src.params.provider.server.name === 'pdbj'
? getPdbjDownloadParams(src)
: src.params.provider.server.name === 'rcsb'
? getRcsbDownloadParams(src)
: assertUnreachable(src as never)
);
asTrajectory = !!src.params.options.asTrajectory;
break;
@@ -205,6 +206,27 @@ async function getDownloadParams(src: string, url: (id: string) => string | Prom
return ret;
}
async function getPdbeDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbe') throw new Error('expected pdbe');
return src.params.provider.server.params.variant === 'updated'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
: src.params.provider.server.params.variant === 'updated-bcif'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
: getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false);
}
async function getPdbjDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbj') throw new Error('expected pdbj');
return getDownloadParams(src.params.provider.id, id => `https://data.pdbjbk1.pdbj.org/pub/pdb/data/structures/divided/mmCIF/${id.toLowerCase().substring(1, 3)}/${id.toLowerCase()}.cif`, id => `PDBj: ${id} (cif)`, false);
}
async function getRcsbDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'rcsb') throw new Error('expected rcsb');
return src.params.provider.server.params.encoding === 'cif'
? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB PDB: ${id} (cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB PDB: ${id} (bcif)`, true);
}
export const UpdateTrajectory = StateAction.build({
display: { name: 'Update Trajectory' },
params: {

View File

@@ -245,7 +245,6 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
}
private onUpdate(ref: string, oldObj: PSO.Molecule.Structure | undefined, obj: PSO.Molecule.Structure) {
// no change to structure
if (oldObj === obj || oldObj?.data === obj.data) return;
@@ -253,7 +252,9 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
const cell = this.plugin.helpers.substructureParent.get(obj.data, true);
if (!cell) return;
ref = cell.transform.ref;
// only need to update the root
if (ref !== cell.transform.ref) return;
if (!this.entries.has(ref)) return;
// use structure from last decorator as reference

View File

@@ -120,7 +120,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
),
sizeTheme: PD.Mapped<any>(
type.defaultSizeTheme.name,
themeCtx.sizeThemeRegistry.types,
themeCtx.sizeThemeRegistry.getApplicableTypes(dataCtx),
name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
)
});
@@ -823,7 +823,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
),
sizeTheme: PD.Mapped<any>(
type.defaultSizeTheme.name,
themeCtx.sizeThemeRegistry.types,
themeCtx.sizeThemeRegistry.getApplicableTypes(dataCtx),
name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
)
});

View File

@@ -57,7 +57,7 @@ export const HighlightLoci = PluginBehavior.create({
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
const loci = this.getLoci(current.loci);
if (this.params.ignore?.indexOf(loci.kind) >= 0) {
if (this.params.ignore.includes(loci.kind)) {
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci });
return;
}
@@ -161,7 +161,7 @@ export const SelectLoci = PluginBehavior.create({
if (!this.ctx.canvas3d || this.ctx.isBusy || !this.ctx.selectionMode) return;
const loci = this.getLoci(current.loci);
if (this.params.ignore?.indexOf(loci.kind) >= 0) return;
if (this.params.ignore.includes(loci.kind)) return;
// only trigger the 1st action that matches
for (const [binding, action, condition] of actions) {
@@ -186,7 +186,7 @@ export const SelectLoci = PluginBehavior.create({
Structure.areEquivalent(structure, oldStructure) &&
Structure.areHierarchiesEqual(structure, oldStructure)) return;
const reprs = this.ctx.state.data.select(StateSelection.Generators.ofType(SO.Molecule.Structure.Representation3D, ref));
const reprs = this.ctx.state.data.select(StateSelection.children(ref).ofType(SO.Molecule.Structure.Representation3D));
for (const repr of reprs) this.applySelectMark(repr.transform.ref, true);
}
});

View File

@@ -10,6 +10,7 @@ import { PluginContext } from './context';
import { PdbDownloadProvider } from '../mol-plugin-state/actions/structure';
import { EmdbDownloadProvider } from '../mol-plugin-state/actions/volume';
import { StructureRepresentationPresetProvider } from '../mol-plugin-state/builder/structure/representation-preset';
import { PluginFeatureDetection } from './features';
export class PluginConfigItem<T = any> {
toString() { return this.key; }
@@ -19,31 +20,6 @@ export class PluginConfigItem<T = any> {
function item<T>(key: string, defaultValue?: T) { return new PluginConfigItem(key, defaultValue); }
export function preferWebGl1() {
if (typeof navigator === 'undefined' || typeof window === 'undefined') return false;
// WebGL2 isn't working in MacOS 12.0.1 Safari 15.1, 15.2. It is working in Safari 15.4 tech preview, so disabling all versions before that.
// prefer webgl 1 based on the userAgent substring
const unpportedSafariVersions = [
'Version/15.1 Safari',
'Version/15.2 Safari',
'Version/15.3 Safari'
];
if (unpportedSafariVersions.some(v => navigator.userAgent.indexOf(v) > 0)) {
return true;
}
// Check for iOS device which enabled WebGL2 recently but it doesn't seem
// to be full up to speed yet.
// adapted from https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isAppleDevice = navigator.userAgent.includes('Macintosh');
const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
}
export const PluginConfig = {
item,
General: {
@@ -53,10 +29,10 @@ export const PluginConfig = {
PixelScale: item('plugin-config.pixel-scale', 1),
PickScale: item('plugin-config.pick-scale', 0.25),
PickPadding: item('plugin-config.pick-padding', 3),
EnableWboit: item('plugin-config.enable-wboit', true),
EnableWboit: item('plugin-config.enable-wboit', PluginFeatureDetection.wboit),
// as of Oct 1 2021, WebGL 2 doesn't work on iOS 15.
// TODO: check back in a few weeks to see if it was fixed
PreferWebGl1: item('plugin-config.prefer-webgl1', preferWebGl1()),
PreferWebGl1: item('plugin-config.prefer-webgl1', PluginFeatureDetection.preferWebGl1),
},
State: {
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export const PluginFeatureDetection = {
get preferWebGl1() {
if (typeof navigator === 'undefined' || typeof window === 'undefined') return false;
// WebGL2 isn't working in MacOS 12.0.1 Safari 15.1, 15.2. It is working in Safari 15.4 tech preview, so disabling all versions before that.
// prefer webgl 1 based on the userAgent substring
const unpportedSafariVersions = [
'Version/15.1 Safari',
'Version/15.2 Safari',
'Version/15.3 Safari'
];
if (unpportedSafariVersions.some(v => navigator.userAgent.indexOf(v) > 0)) {
return true;
}
// Check for iOS device which enabled WebGL2 recently but it doesn't seem
// to be full up to speed yet.
// adapted from https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isAppleDevice = navigator.userAgent.includes('Macintosh');
const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
},
get wboit() {
if (typeof navigator === 'undefined' || typeof window === 'undefined') return true;
// disable Wboit in Safari 15
return !/Version\/15.\d Safari/.test(navigator.userAgent);
}
};

View File

@@ -84,7 +84,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
// Remap `loci` from equivalent structure to the current `_structure`
loci = Loci.remap(loci, _structure);
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
if (Structure.isLoci(loci) || (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci))) {
// Change to `EveryLoci` to allow for downstream optimizations
loci = EveryLoci;
}

View File

@@ -44,6 +44,6 @@ export const GaussianSurfaceRepresentationProvider = StructureRepresentationProv
getParams: getGaussianSurfaceParams,
defaultValues: PD.getDefaultValues(GaussianSurfaceParams),
defaultColorTheme: { name: 'chain-id' },
defaultSizeTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'physical' },
isApplicable: (structure: Structure) => structure.elementCount > 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>
*/
@@ -10,7 +10,6 @@ import { StructureRepresentation, StructureRepresentationProvider, ComplexRepres
import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
import { ThemeRegistryContext } from '../../../mol-theme/theme';
import { Structure } from '../../../mol-model/structure';
import { DirectVolume } from '../../../mol-geo/geometry/direct-volume/direct-volume';
const GaussianVolumeVisuals = {
'gaussian-volume': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianDensityVolumeParams>) => ComplexRepresentation('Gaussian volume', ctx, getParams, GaussianDensityVolumeVisual),
@@ -24,10 +23,6 @@ export const GaussianVolumeParams = {
export type GaussianVolumeParams = typeof GaussianVolumeParams
export function getGaussianVolumeParams(ctx: ThemeRegistryContext, structure: Structure) {
const p = PD.clone(GaussianVolumeParams);
p.renderMode = DirectVolume.createRenderModeParam({
// TODO find a better way to set
min: 0, max: 1, mean: 0.04, sigma: 0.01
});
p.jumpLength = PD.Numeric(4, { min: 0, max: 20, step: 0.1 });
return p;
}
@@ -45,6 +40,6 @@ export const GaussianVolumeRepresentationProvider = StructureRepresentationProvi
getParams: getGaussianVolumeParams,
defaultValues: PD.getDefaultValues(GaussianVolumeParams),
defaultColorTheme: { name: 'chain-id' },
defaultSizeTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'physical' },
isApplicable: (structure: Structure) => structure.elementCount > 0
});

View File

@@ -40,6 +40,6 @@ export const LabelRepresentationProvider = StructureRepresentationProvider({
getParams: getLabelParams,
defaultValues: PD.getDefaultValues(LabelParams),
defaultColorTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'physical' },
isApplicable: (structure: Structure) => structure.elementCount > 0
});

View File

@@ -43,6 +43,6 @@ export const MolecularSurfaceRepresentationProvider = StructureRepresentationPro
getParams: getMolecularSurfaceParams,
defaultValues: PD.getDefaultValues(MolecularSurfaceParams),
defaultColorTheme: { name: 'chain-id' },
defaultSizeTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'physical' },
isApplicable: (structure: Structure) => structure.elementCount > 0
});

View File

@@ -202,7 +202,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
// Remap `loci` from equivalent structure to the current `_structure`
loci = Loci.remap(loci, _structure);
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
if (Structure.isLoci(loci) || (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci))) {
// Change to `EveryLoci` to allow for downstream optimizations
loci = EveryLoci;
}

View File

@@ -25,14 +25,14 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct
}
const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
const densityTextureData = await computeStructureGaussianDensityTexture(structure, props, webgl, oldTexture).runInContext(runtime);
const densityTextureData = await computeStructureGaussianDensityTexture(structure, theme.size, props, webgl, oldTexture).runInContext(runtime);
const { transform, texture, bbox, gridDim } = densityTextureData;
const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
const cellDim = Mat4.getScaling(Vec3(), transform);
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume);
const axisOrder = Vec3.create(0, 1, 2);
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, directVolume);
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
vol.setBoundingSphere(sphere);
@@ -81,14 +81,14 @@ async function createUnitsGaussianDensityVolume(ctx: VisualContext, unit: Unit,
}
const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, props, webgl, oldTexture).runInContext(runtime);
const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, theme.size, props, webgl, oldTexture).runInContext(runtime);
const { transform, texture, bbox, gridDim } = densityTextureData;
const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
const cellDim = Mat4.getScaling(Vec3(), transform);
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume);
const axisOrder = Vec3.create(0, 1, 2);
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, directVolume);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getUnitExtraRadius(unit));
vol.setBoundingSphere(sphere);

View File

@@ -26,6 +26,7 @@ import { Texture } from '../../../mol-gl/webgl/texture';
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';
import { Vec3 } from '../../../mol-math/linear-algebra';
const SharedParams = {
...GaussianDensityParams,
@@ -88,7 +89,7 @@ type GaussianSurfaceMeta = {
async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
const { smoothness } = props;
const { transform, field, idField, radiusFactor, resolution } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
const { transform, field, idField, radiusFactor, resolution } = await computeUnitGaussianDensity(structure, unit, theme.size, props).runInContext(ctx.runtime);
const params = {
isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -149,7 +150,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
const { smoothness } = props;
const { transform, field, idField, radiusFactor, resolution } = await computeStructureGaussianDensity(structure, props).runInContext(ctx.runtime);
const { transform, field, idField, radiusFactor, resolution } = await computeStructureGaussianDensity(structure, theme.size, props).runInContext(ctx.runtime);
const params = {
isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -222,7 +223,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
}
// console.time('computeUnitGaussianDensityTexture2d');
const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, theme.size, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
// console.log(densityTextureData);
// console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture));
// ctx.webgl.waitForGpuCommandsCompleteSync();
@@ -230,8 +231,9 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
const axisOrder = Vec3.create(0, 1, 2);
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
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);
@@ -298,7 +300,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
}
// console.time('computeUnitGaussianDensityTexture2d');
const densityTextureData = await computeStructureGaussianDensityTexture2d(structure, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
const densityTextureData = await computeStructureGaussianDensityTexture2d(structure, theme.size, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
// console.log(densityTextureData);
// console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture));
// ctx.webgl.waitForGpuCommandsCompleteSync();
@@ -306,8 +308,9 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
const axisOrder = Vec3.create(0, 1, 2);
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
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);

View File

@@ -19,7 +19,7 @@ import { getUnitExtraRadius } from './util/common';
async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
const { smoothness } = props;
const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, theme.size, props).runInContext(ctx.runtime);
const params = {
isoLevel: Math.exp(-smoothness),

View File

@@ -16,7 +16,6 @@ import { ComplexTextVisual, ComplexTextParams, ComplexVisual } from '../complex-
import { ElementIterator, getSerialElementLoci, eachSerialElement } from './util/element';
import { ColorNames } from '../../../mol-util/color/names';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { PhysicalSizeTheme } from '../../../mol-theme/size/physical';
import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
export const LabelTextParams = {
@@ -151,7 +150,7 @@ function createElementText(ctx: VisualContext, structure: Structure, theme: Them
const { label_atom_id, label_alt_id } = StructureProperties.atom;
const { cumulativeUnitElementCount } = serialMapping;
const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
const sizeTheme = theme.size;
const count = structure.elementCount;
const { elementScale } = props;

View File

@@ -40,7 +40,7 @@ type MolecularSurfaceMeta = {
//
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> {
const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, theme.size, props).runInContext(ctx.runtime);
const params = {
isoLevel: props.probeRadius,

View File

@@ -29,7 +29,7 @@ export type MolecularSurfaceWireframeParams = typeof MolecularSurfaceWireframePa
//
async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, lines?: Lines): Promise<Lines> {
const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, theme.size, props).runInContext(ctx.runtime);
const params = {
isoLevel: props.probeRadius,
scalarField: field,

View File

@@ -9,13 +9,13 @@ import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
import { TransformData, createTransform } from '../../../../mol-geo/geometry/transform-data';
import { OrderedSet, SortedArray } from '../../../../mol-data/int';
import { EmptyLoci, Loci } from '../../../../mol-model/loci';
import { PhysicalSizeTheme } from '../../../../mol-theme/size/physical';
import { AtomicNumbers } from '../../../../mol-model/structure/model/properties/atomic';
import { fillSerial } from '../../../../mol-util/array';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { AssignableArrayLike } from '../../../../mol-util/type-helpers';
import { getBoundary } from '../../../../mol-math/geometry/boundary';
import { Box3D } from '../../../../mol-math/geometry';
import { SizeTheme } from '../../../../mol-theme/size';
/** Return a Loci for the elements of a whole residue the elementIndex belongs to. */
export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci {
@@ -165,7 +165,7 @@ function filterId(id: AssignableArrayLike<number>, elements: SortedArray, indice
}
}
export function getUnitConformationAndRadius(structure: Structure, unit: Unit, props: CommonSurfaceProps) {
export function getUnitConformationAndRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: CommonSurfaceProps) {
const { ignoreHydrogens, traceOnly, includeParent } = props;
const rootUnit = includeParent ? structure.root.unitMap.get(unit.id) : unit;
@@ -205,7 +205,6 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
const boundary = unit === rootUnit ? unit.boundary : getBoundary(position);
const l = StructureElement.Location.create(structure, rootUnit);
const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
const radius = (index: number) => {
l.element = index as ElementIndex;
return sizeTheme.size(l);
@@ -214,9 +213,8 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
return { position, boundary, radius };
}
export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens: boolean, traceOnly: boolean) {
export function getStructureConformationAndRadius(structure: Structure, sizeTheme: SizeTheme<any>, ignoreHydrogens: boolean, traceOnly: boolean) {
const l = StructureElement.Location.create(structure);
const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
let xs: ArrayLike<number>;
let ys: ArrayLike<number>;

View File

@@ -13,6 +13,7 @@ import { WebGLContext } from '../../../../mol-gl/webgl/context';
import { getUnitConformationAndRadius, getStructureConformationAndRadius, CommonSurfaceParams, ensureReasonableResolution } from './common';
import { BaseGeometry } from '../../../../mol-geo/geometry/base';
import { GaussianDensityCPU } from '../../../../mol-math/geometry/gaussian-density/cpu';
import { SizeTheme } from '../../../../mol-theme/size';
export const GaussianDensityParams = {
resolution: PD.Numeric(1, { min: 0.1, max: 20, step: 0.1 }, { description: 'Grid resolution/cell spacing.', ...BaseGeometry.CustomQualityParamInfo }),
@@ -32,28 +33,28 @@ export function getTextureMaxCells(webgl: WebGLContext, structure?: Structure) {
//
export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps) {
export function computeUnitGaussianDensity(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: GaussianDensityProps) {
const { box } = unit.lookup3d.boundary;
const p = ensureReasonableResolution(box, props);
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
return Task.create('Gaussian Density', async ctx => {
return await GaussianDensityCPU(ctx, position, box, radius, p);
});
}
export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
const { box } = unit.lookup3d.boundary;
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
return Task.create('Gaussian Density', async ctx => {
return GaussianDensityTexture(webgl, position, box, radius, p, texture);
});
}
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
const { box } = unit.lookup3d.boundary;
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
return Task.create('Gaussian Density', async ctx => {
return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
});
@@ -61,28 +62,28 @@ export function computeUnitGaussianDensityTexture2d(structure: Structure, unit:
//
export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps) {
export function computeStructureGaussianDensity(structure: Structure, sizeTheme: SizeTheme<any>, props: GaussianDensityProps) {
const { box } = structure.lookup3d.boundary;
const p = ensureReasonableResolution(box, props);
const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
return Task.create('Gaussian Density', async ctx => {
return await GaussianDensityCPU(ctx, position, box, radius, p);
});
}
export function computeStructureGaussianDensityTexture(structure: Structure, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
export function computeStructureGaussianDensityTexture(structure: Structure, sizeTheme: SizeTheme<any>, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
const { box } = structure.lookup3d.boundary;
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
return Task.create('Gaussian Density', async ctx => {
return GaussianDensityTexture(webgl, position, box, radius, p, texture);
});
}
export function computeStructureGaussianDensityTexture2d(structure: Structure, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
export function computeStructureGaussianDensityTexture2d(structure: Structure, sizeTheme: SizeTheme<any>, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
const { box } = structure.lookup3d.boundary;
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
return Task.create('Gaussian Density', async ctx => {
return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
});

View File

@@ -11,12 +11,13 @@ import { PositionData, DensityData, Box3D } from '../../../../mol-math/geometry'
import { MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
import { OrderedSet } from '../../../../mol-data/int';
import { Boundary } from '../../../../mol-math/geometry/boundary';
import { SizeTheme } from '../../../../mol-theme/size';
export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & CommonSurfaceProps
function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
const { probeRadius } = props;
const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, props);
const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, props);
const { indices } = position;
const n = OrderedSet.size(indices);
const radii = new Float32Array(OrderedSet.end(indices));
@@ -32,10 +33,10 @@ function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: Mo
return { position: { ...position, radius: radii }, boundary, maxRadius };
}
export function computeUnitMolecularSurface(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
export function computeUnitMolecularSurface(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
const { box } = unit.lookup3d.boundary;
const p = ensureReasonableResolution(box, props);
const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, p);
const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, sizeTheme, p);
return Task.create('Molecular Surface', async ctx => {
return await MolecularSurface(ctx, position, boundary, maxRadius, box, p);
});

View File

@@ -48,7 +48,8 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
texture.load(textureImage);
const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume);
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, directVolume);
}
// 3d volume texture
@@ -89,7 +90,8 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
texture.load(textureVolume);
const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume);
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, directVolume);
}
//
@@ -104,9 +106,7 @@ export async function createDirectVolume(ctx: VisualContext, volume: Volume, the
}
function getLoci(volume: Volume, props: PD.Values<DirectVolumeParams>) {
return props.renderMode.name === 'isosurface'
? Volume.Isosurface.Loci(volume, props.renderMode.params.isoValue)
: Volume.Loci(volume);
return Volume.Loci(volume);
}
export function getDirectVolumeLoci(pickingId: PickingId, volume: Volume, props: DirectVolumeProps, id: number) {
@@ -118,9 +118,7 @@ export function getDirectVolumeLoci(pickingId: PickingId, volume: Volume, props:
}
export function eachDirectVolume(loci: Loci, volume: Volume, props: DirectVolumeProps, apply: (interval: Interval) => boolean) {
const isoValue = props.renderMode.name === 'isosurface'
? props.renderMode.params.isoValue : undefined;
return eachVolumeLoci(loci, volume, isoValue, apply);
return eachVolumeLoci(loci, volume, undefined, apply);
}
//
@@ -131,9 +129,7 @@ export const DirectVolumeParams = {
};
export type DirectVolumeParams = typeof DirectVolumeParams
export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
const p = PD.clone(DirectVolumeParams);
p.renderMode = DirectVolume.createRenderModeParam(volume.grid.stats);
return p;
return PD.clone(DirectVolumeParams);
}
export type DirectVolumeProps = PD.Values<DirectVolumeParams>
@@ -164,7 +160,7 @@ export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
factory: DirectVolumeRepresentation,
getParams: getDirectVolumeParams,
defaultValues: PD.getDefaultValues(DirectVolumeParams),
defaultColorTheme: { name: 'uniform' },
defaultColorTheme: { name: 'volume-value' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
});

View File

@@ -43,6 +43,10 @@ function gpuSupport(webgl: WebGLContext) {
const Padding = 1;
function suitableForGpu(volume: Volume, webgl: WebGLContext) {
// small volumes are about as fast or faster on CPU vs integrated GPU
if (volume.grid.cells.data.length < Math.pow(10, 3)) return false;
// the GPU is much more memory contraint, especially true for integrated GPUs,
// fallback to CPU for large volumes
const gridDim = volume.grid.cells.space.dimensions as Vec3;
const { powerOfTwoSize } = getVolumeTexture2dLayout(gridDim, Padding);
return powerOfTwoSize <= webgl.maxTextureSize / 2;
@@ -131,7 +135,6 @@ namespace VolumeIsosurfaceTexture {
export function get(volume: Volume, webgl: WebGLContext) {
const { resources } = webgl;
const transform = Grid.getGridToCartesianTransform(volume.grid);
const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
@@ -169,6 +172,10 @@ namespace VolumeIsosurfaceTexture {
async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, theme: Theme, props: VolumeIsosurfaceProps, textureMesh?: TextureMesh) {
if (!ctx.webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
if (volume.grid.cells.data.length <= 1) {
return TextureMesh.createEmpty(textureMesh);
}
const { max, min } = volume.grid.stats;
const diff = max - min;
const value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
@@ -176,8 +183,9 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);

View File

@@ -21,7 +21,7 @@ import { transformPositionArray } from '../../mol-geo/util';
import { RenderableState } from '../../mol-gl/renderable';
import { Color } from '../../mol-util/color';
import { ColorTheme } from '../../mol-theme/color';
import { encodeFloatRGBtoArray } from '../../mol-util/float-packing';
import { packIntToRGBArray } from '../../mol-util/number-packing';
import { eachVolumeLoci } from './util';
export async function createImage(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
@@ -116,7 +116,7 @@ function getPackedGroupArray(grid: Grid, props: PD.Values<SliceParams>) {
for (let iy = y0; iy < ny; ++iy) {
for (let ix = x0; ix < nx; ++ix) {
for (let iz = z0; iz < nz; ++iz) {
encodeFloatRGBtoArray(space.dataOffset(ix, iy, iz), groupArray, j);
packIntToRGBArray(space.dataOffset(ix, iy, iz), groupArray, j);
j += 4;
}
}

View File

@@ -9,7 +9,7 @@ import { Loci } from '../../mol-model/loci';
import { Interval, OrderedSet } from '../../mol-data/int';
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { encodeFloatRGBtoArray } from '../../mol-util/float-packing';
import { packIntToRGBArray } from '../../mol-util/number-packing';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3set = Vec3.set;
@@ -110,7 +110,7 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
array[index] = Math.round(((data[offset] - min) / diff) * 255);
} else {
if (variant === 'groups') {
encodeFloatRGBtoArray(offset, array, index);
packIntToRGBArray(offset, array, index);
} else {
v3set(n0,
data[o(Math.max(0, x - 1), y, z)],

View File

@@ -36,6 +36,7 @@ import { PartialChargeColorThemeProvider } from './color/partial-charge';
import { AtomIdColorThemeProvider } from './color/atom-id';
import { EntityIdColorThemeProvider } from './color/entity-id';
import { TextureFilter } from '../mol-gl/webgl/texture';
import { VolumeValueColorThemeProvider } from './color/volume-value';
export type LocationColor = (location: Location, isSecondary: boolean) => Color
@@ -120,6 +121,7 @@ namespace ColorTheme {
'uncertainty': UncertaintyColorThemeProvider,
'unit-index': UnitIndexColorThemeProvider,
'uniform': UniformColorThemeProvider,
'volume-value': VolumeValueColorThemeProvider,
};
type _BuiltIn = typeof BuiltIn
export type BuiltIn = keyof _BuiltIn

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ColorTheme } from '../color';
import { Color, ColorScale } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { ColorNames } from '../../mol-util/color/names';
const DefaultColor = Color(0xCCCCCC);
const Description = 'Assign color based on the given value of a volume cell.';
export const VolumeValueColorThemeParams = {
colorList: PD.ColorList({
kind: 'interpolate',
colors: [
[ColorNames.white, 0],
[ColorNames.red, 0.25],
[ColorNames.white, 0.5],
[ColorNames.blue, 0.75],
[ColorNames.white, 1]
]
}, { offsets: true, isEssential: true }),
};
export type VolumeValueColorThemeParams = typeof VolumeValueColorThemeParams
export function getVolumeValueColorThemeParams(ctx: ThemeDataContext) {
return VolumeValueColorThemeParams; // TODO return copy
}
export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueColorThemeParams>): ColorTheme<VolumeValueColorThemeParams> {
const scale = ColorScale.create({ domain: [0, 1], listOrName: props.colorList.colors });
const colors: Color[] = [];
for (let i = 0; i < 256; ++i) {
colors[i] = scale.color(i / 255);
}
const palette: ColorTheme.Palette = { colors, filter: 'linear' };
return {
factory: VolumeValueColorTheme,
granularity: 'direct',
color: () => DefaultColor,
props: props,
description: Description,
legend: scale.legend,
palette,
};
}
export const VolumeValueColorThemeProvider: ColorTheme.Provider<VolumeValueColorThemeParams, 'volume-value'> = {
name: 'volume-value',
label: 'Volume Value',
category: ColorTheme.Category.Misc,
factory: VolumeValueColorTheme,
getParams: getVolumeValueColorThemeParams,
defaultValues: PD.getDefaultValues(VolumeValueColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.volume,
};

View File

@@ -1,26 +1,16 @@
/**
* 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 { clamp } from '../mol-math/interpolate';
import { fasterExp, fasterLog } from '../mol-math/approx';
import { Vec3, Vec4 } from '../mol-math/linear-algebra';
import { NumberArray } from './type-helpers';
const maxFloat = 10000.0; // NOTE same constant is set in shaders
const floatLogFactor = fasterLog(maxFloat + 1);
/** encode float logarithmically */
export function encodeFloatLog(value: number) { return fasterLog(value + 1) / floatLogFactor; }
/** decode logarithmically encoded float */
export function decodeFloatLog(value: number) { return fasterExp(value * floatLogFactor) - 1; }
/** encode float as rgb triplet into array at offset */
export function encodeFloatRGBtoArray(value: number, array: NumberArray, offset: number) {
value = clamp(value, 0, 16777216 - 1) + 1;
/** encode positive integer as rgb byte triplet into array at offset */
export function packIntToRGBArray(value: number, array: NumberArray, offset: number) {
value = clamp(Math.round(value), 0, 16777216 - 1) + 1;
array[offset + 2] = value % 256;
value = Math.floor(value / 256);
array[offset + 1] = value % 256;
@@ -29,8 +19,8 @@ export function encodeFloatRGBtoArray(value: number, array: NumberArray, offset:
return array;
}
/** decode float encoded as rgb triplet */
export function decodeFloatRGB(r: number, g: number, b: number) {
/** decode positive integer encoded as rgb byte triplet */
export function unpackRGBToInt(r: number, g: number, b: number) {
return (Math.floor(r) * 256 * 256 + Math.floor(g) * 256 + Math.floor(b)) - 1;
}

View File

@@ -73,7 +73,7 @@ async function init() {
console.timeEnd('gpu mc pyramid2');
console.time('gpu mc vert2');
createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true);
createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true, Vec3.create(0, 1, 2));
webgl.waitForGpuCommandsCompleteSync();
console.timeEnd('gpu mc vert2');
console.timeEnd('gpu mc2');
@@ -96,7 +96,7 @@ async function init() {
console.timeEnd('gpu mc pyramid');
console.time('gpu mc vert');
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true);
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2));
webgl.waitForGpuCommandsCompleteSync();
console.timeEnd('gpu mc vert');
console.timeEnd('gpu mc');