mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 05:44:23 +08:00
Compare commits
276 Commits
safari-deb
...
v3.19.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9624137c0d | ||
|
|
3eb433368f | ||
|
|
58691f4f5f | ||
|
|
5e9295abd5 | ||
|
|
6ed0ae55b2 | ||
|
|
84448d0aa1 | ||
|
|
31ced24966 | ||
|
|
24681840af | ||
|
|
5d28aa4f2e | ||
|
|
7dabdf3085 | ||
|
|
80011d4aea | ||
|
|
c6fe440a01 | ||
|
|
ba8d6dc3fa | ||
|
|
aa414485a5 | ||
|
|
3a35a5d66a | ||
|
|
43b0a72b09 | ||
|
|
521ac2d13f | ||
|
|
30520c50c2 | ||
|
|
819f07eba3 | ||
|
|
d8d6aa7136 | ||
|
|
ed75a365d8 | ||
|
|
f5ff13ffe4 | ||
|
|
44c69b1716 | ||
|
|
559ca7ffb8 | ||
|
|
524f34e8c1 | ||
|
|
d749be11f0 | ||
|
|
13dc9ff3cb | ||
|
|
24b4fce326 | ||
|
|
f506210bf8 | ||
|
|
cb0cbd06ce | ||
|
|
3356239089 | ||
|
|
9a5b2edc08 | ||
|
|
2d41b4bd83 | ||
|
|
58f7758ee1 | ||
|
|
9dbb642883 | ||
|
|
c5222e4d1d | ||
|
|
a5a695a17c | ||
|
|
7d1dc86cfb | ||
|
|
03224f914a | ||
|
|
1cf1f07232 | ||
|
|
838d36a74e | ||
|
|
6c9300d01b | ||
|
|
3059f7efef | ||
|
|
fbce7d9afa | ||
|
|
1c9f3ed9fa | ||
|
|
8c47d2d400 | ||
|
|
8a18f25b5d | ||
|
|
e7ae0058ed | ||
|
|
98bf3a3e33 | ||
|
|
379fcd4494 | ||
|
|
8589777bac | ||
|
|
c10a21ecbd | ||
|
|
eddc616b14 | ||
|
|
70fc1a9579 | ||
|
|
f27ec4d6a4 | ||
|
|
1e6e956e2d | ||
|
|
0a2a3530d1 | ||
|
|
9e4c820e26 | ||
|
|
05290bfe9e | ||
|
|
607f4ce353 | ||
|
|
4b819ead1d | ||
|
|
d07d9d3f31 | ||
|
|
d08776bf19 | ||
|
|
7a61fd44fd | ||
|
|
151da1487c | ||
|
|
e3f6dfad5b | ||
|
|
32080ce918 | ||
|
|
aedb2138c8 | ||
|
|
90e6938f1c | ||
|
|
eadff35250 | ||
|
|
487450ec64 | ||
|
|
f2f730bab5 | ||
|
|
ceecee37a7 | ||
|
|
394377bea9 | ||
|
|
2b47818deb | ||
|
|
2935717a06 | ||
|
|
2c8d2cfa21 | ||
|
|
d67c0eb757 | ||
|
|
7bcbcd5a7f | ||
|
|
1c06e7f36e | ||
|
|
407297adc0 | ||
|
|
b082b31562 | ||
|
|
fcbeb0f82f | ||
|
|
7094f8f265 | ||
|
|
48aaa13420 | ||
|
|
d2434cf91f | ||
|
|
8dbe0d2793 | ||
|
|
7b308cf984 | ||
|
|
520af504aa | ||
|
|
4bee130599 | ||
|
|
19a9ed3e19 | ||
|
|
0dac1b93ae | ||
|
|
e824863de1 | ||
|
|
9ff8becd62 | ||
|
|
fcaa1bcfa8 | ||
|
|
39f51bcc4f | ||
|
|
1b904ee2c9 | ||
|
|
e2baafc426 | ||
|
|
6dabe73002 | ||
|
|
d9b2b99c86 | ||
|
|
bdf23a7c4e | ||
|
|
c1723e0806 | ||
|
|
93590bd482 | ||
|
|
fbaa9d9e58 | ||
|
|
ba78a8558c | ||
|
|
513be04352 | ||
|
|
15317aa11b | ||
|
|
93e107f333 | ||
|
|
655b334b0a | ||
|
|
95cc1c58a6 | ||
|
|
0511d3e599 | ||
|
|
92a41b5c46 | ||
|
|
be16837c8c | ||
|
|
bf5f26cb12 | ||
|
|
ccbcef7eff | ||
|
|
d02a97b7f0 | ||
|
|
e0ca413c54 | ||
|
|
a272fc1c05 | ||
|
|
2c3f74d4ea | ||
|
|
c65b2fc0fd | ||
|
|
fd725adf27 | ||
|
|
ceba6da91f | ||
|
|
064e7d42ee | ||
|
|
cfdbf0c614 | ||
|
|
436777fe34 | ||
|
|
f08aa46222 | ||
|
|
e099ac514a | ||
|
|
873755f619 | ||
|
|
2094b7cf83 | ||
|
|
6ffdd81bb1 | ||
|
|
a69cb17602 | ||
|
|
7c82a9fd6e | ||
|
|
10d9a6c83d | ||
|
|
f6ed650ef6 | ||
|
|
df9ce6add9 | ||
|
|
28ee47d501 | ||
|
|
df2da479ad | ||
|
|
46eb9d8baf | ||
|
|
b6be871a21 | ||
|
|
ce2367fc0a | ||
|
|
7789e8cfea | ||
|
|
51a9effcaa | ||
|
|
fc3b953a8e | ||
|
|
a2ded3cecc | ||
|
|
ffb1390b51 | ||
|
|
3b92591c05 | ||
|
|
f173ddcf00 | ||
|
|
f78306f624 | ||
|
|
9852c9301e | ||
|
|
2e4f3de604 | ||
|
|
300dd97353 | ||
|
|
8e29ade83a | ||
|
|
971c770f6a | ||
|
|
7d31a06ae4 | ||
|
|
c5319ad7b1 | ||
|
|
f8bdb28ea6 | ||
|
|
2f8806d7c2 | ||
|
|
7d0a181c12 | ||
|
|
27cb7e53ed | ||
|
|
ee5154b510 | ||
|
|
080837201a | ||
|
|
656e6c0d94 | ||
|
|
b018f61bab | ||
|
|
b03b306848 | ||
|
|
19bf5c2b3e | ||
|
|
c22a716cf9 | ||
|
|
220c65da92 | ||
|
|
675f4b86f8 | ||
|
|
d31b3522b2 | ||
|
|
4ed2bab1d7 | ||
|
|
a572872806 | ||
|
|
e3ca23db0b | ||
|
|
67eb16a53f | ||
|
|
d7421cd1a3 | ||
|
|
c2e68ced66 | ||
|
|
10cf0db050 | ||
|
|
08f1a1dcfe | ||
|
|
11bf352295 | ||
|
|
5fd560b30a | ||
|
|
e6d01ca246 | ||
|
|
0ddf2fa00d | ||
|
|
8776143ec2 | ||
|
|
080c8e7af3 | ||
|
|
64998e762b | ||
|
|
b508da5ccc | ||
|
|
84a492655a | ||
|
|
9b9cfe4138 | ||
|
|
f362a7086a | ||
|
|
9e9ec57a5f | ||
|
|
da6a153985 | ||
|
|
b4bde3f510 | ||
|
|
8a5cebd635 | ||
|
|
ddf2733d3c | ||
|
|
cf65bfbcd0 | ||
|
|
03cce830bc | ||
|
|
913cf4c3e9 | ||
|
|
2ccce0beaf | ||
|
|
52239f71cd | ||
|
|
d9265af2e8 | ||
|
|
5877f6a627 | ||
|
|
e3e982c051 | ||
|
|
17f09ff3de | ||
|
|
7a73416c03 | ||
|
|
0f799d44ad | ||
|
|
24ebd44f87 | ||
|
|
572b10e655 | ||
|
|
60361c176b | ||
|
|
b232a2c58f | ||
|
|
2a44ac56fb | ||
|
|
d0340a3257 | ||
|
|
e708a53ddb | ||
|
|
23cdd70198 | ||
|
|
ba4bc30a78 | ||
|
|
e516ea146d | ||
|
|
61af638fe4 | ||
|
|
7a0af4142f | ||
|
|
1aa8d596a3 | ||
|
|
a457810623 | ||
|
|
4bccb7ab84 | ||
|
|
fcd5b2ce0a | ||
|
|
57a1184a16 | ||
|
|
ef176efed8 | ||
|
|
9943e0317d | ||
|
|
ae11c1c904 | ||
|
|
f937e069ca | ||
|
|
c9326da47b | ||
|
|
4698c05f9c | ||
|
|
15fd2cd5a0 | ||
|
|
193eb11095 | ||
|
|
59cc0096cd | ||
|
|
6bd7390eb8 | ||
|
|
eabeb18f34 | ||
|
|
4b6f539ba3 | ||
|
|
ea55fc5f41 | ||
|
|
94787229a4 | ||
|
|
0cb162022c | ||
|
|
5ff2ca311e | ||
|
|
44b8d452a8 | ||
|
|
82ccb1ab23 | ||
|
|
feb8b94e30 | ||
|
|
5adc73e277 | ||
|
|
c4ac983fc7 | ||
|
|
5ba7ba3aac | ||
|
|
e879479b3d | ||
|
|
7b49463297 | ||
|
|
66600c3373 | ||
|
|
19401c4bc6 | ||
|
|
bfc8660c5e | ||
|
|
6a83dc56ba | ||
|
|
82df9d8cad | ||
|
|
dd30fef078 | ||
|
|
79feb5a1cc | ||
|
|
0665524b11 | ||
|
|
d45367e840 | ||
|
|
cb0d988efc | ||
|
|
fc0c556967 | ||
|
|
00970164db | ||
|
|
7c3d76e9fe | ||
|
|
190c1f9620 | ||
|
|
f532325147 | ||
|
|
278dcb8808 | ||
|
|
309c25e10b | ||
|
|
6df728ea3e | ||
|
|
dcf4ef6d74 | ||
|
|
4de1369a5a | ||
|
|
2ccfdb1280 | ||
|
|
9fbf800639 | ||
|
|
577daf64df | ||
|
|
0b1943e9b3 | ||
|
|
30bd2dd876 | ||
|
|
cecd4d4179 | ||
|
|
364baab18d | ||
|
|
bb3d4d2171 | ||
|
|
2355faf899 | ||
|
|
858e0b24ff | ||
|
|
f7d0ed3988 |
40
CHANGELOG.md
40
CHANGELOG.md
@@ -6,6 +6,46 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.19.0] - 2022-10-01
|
||||
|
||||
- Fix "empty textures" error on empty canvas
|
||||
- Optimize BinaryCIF integer packing encoder
|
||||
- Fix dual depth peeling when post-processing is off or when rendering direct-volumes
|
||||
- Add ``cameraClipping.minNear`` parameter
|
||||
- Fix black artifacts on specular highlights with transparent background
|
||||
|
||||
## [v3.18.0] - 2022-09-17
|
||||
|
||||
- Integration of Dual depth peeling - OIT method
|
||||
- Stereo camera improvements
|
||||
- Fix param updates not applied
|
||||
- Better param ranges and description
|
||||
- Add timer.mark for left/right camera
|
||||
|
||||
## [v3.17.0] - 2022-09-11
|
||||
|
||||
- [Fix] Clone ``Canvas3DParams`` when creating a ``Canvas3D`` instance to prevent shared state between multiple instances
|
||||
- Add ``includeResidueTest`` option to ``alignAndSuperposeWithSIFTSMapping``
|
||||
- Add ``parentDisplay`` param for interactions representation.
|
||||
- [Experimental] Add support for PyMOL, VMD, and Jmol atom expressions in selection scripts
|
||||
- Support for ``failIfMajorPerformanceCaveat`` webgl attribute. Add ``PluginConfig.General.AllowMajorPerformanceCaveat`` and ``allow-major-performance-caveat`` Viewer GET param.
|
||||
- Fix handling of PDB TER records (#549)
|
||||
- Add support for getting multiple loci from a representation (``.getAllLoci()``)
|
||||
- Add ``key`` property to intra- and inter-bonds for referencing source data
|
||||
- Fix click event triggered after move
|
||||
|
||||
## [v3.16.0] - 2022-08-25
|
||||
|
||||
- Support ``globalColorParams`` and ``globalSymmetryParams`` in common representation params
|
||||
- Support ``label`` parameter in ``Viewer.loadStructureFromUrl``
|
||||
- Fix ``ViewportHelpContent`` Mouse Controls section
|
||||
|
||||
## [v3.15.0] - 2022-08-23
|
||||
|
||||
- Fix wboit in Safari >=15 (add missing depth renderbuffer to wboit pass)
|
||||
- Add 'Around Camera' option to Volume streaming
|
||||
- Avoid queuing more than one update in Volume streaming
|
||||
|
||||
## [v3.14.0] - 2022-08-20
|
||||
|
||||
- Expose inter-bonds compute params in structure
|
||||
|
||||
3187
package-lock.json
generated
3187
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.14.0",
|
||||
"version": "3.19.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -89,57 +89,60 @@
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
|
||||
"Adam Midlik <midlik@gmail.com>",
|
||||
"Koya Sakuma <koya.sakuma.work@gmail.com>",
|
||||
"Gianluca Tomasello <giagitom@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.2.1",
|
||||
"@graphql-codegen/cli": "^2.11.6",
|
||||
"@graphql-codegen/cli": "^2.13.1",
|
||||
"@graphql-codegen/time": "^3.2.1",
|
||||
"@graphql-codegen/typescript": "^2.7.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.5",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.3",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.1",
|
||||
"@types/jest": "^28.1.7",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/jest": "^29.1.1",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"@typescript-eslint/parser": "^5.38.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^7.3.0",
|
||||
"concurrently": "^7.4.0",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint": "^8.24.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^28.1.3",
|
||||
"jest": "^29.1.2",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.54.5",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^13.0.2",
|
||||
"simple-git": "^3.12.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^28.0.8",
|
||||
"typescript": "^4.7.4",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.8.4",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/benchmark": "^2.1.2",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.51",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^16.11.62",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
@@ -151,8 +154,8 @@
|
||||
"immer": "^9.0.15",
|
||||
"immutable": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.6",
|
||||
"swagger-ui-dist": "^4.14.0",
|
||||
"rxjs": "^7.5.7",
|
||||
"swagger-ui-dist": "^4.14.2",
|
||||
"tslib": "^2.4.0",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
|
||||
@@ -166,7 +166,7 @@ class Viewer {
|
||||
structures.push({ ref: structureProperties?.ref || structure.ref });
|
||||
}
|
||||
|
||||
// remove current structuresfrom hierarchy as they will be merged
|
||||
// remove current structures from hierarchy as they will be merged
|
||||
// TODO only works with using loadStructuresFromUrlsAndMerge once
|
||||
// need some more API metho to work with the hierarchy
|
||||
this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
@@ -202,14 +202,14 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
|
||||
const components = {
|
||||
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
|
||||
interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
|
||||
interactions: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
};
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, parentDisplay: 'between' }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
};
|
||||
|
||||
|
||||
@@ -88,7 +88,9 @@ const DefaultViewerOptions = {
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
enableDpoit: PluginConfig.General.EnableDpoit.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
@@ -158,7 +160,9 @@ export class Viewer {
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.General.EnableDpoit, o.enableDpoit],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
@@ -199,7 +203,7 @@ export class Viewer {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions & { label?: string }) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
@@ -208,6 +212,7 @@ export class Viewer {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
label: options?.label,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
@@ -496,4 +501,4 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
// viewer.loadStructureFromUrl('my url', 'pdb', false, {
|
||||
// representationParams: {
|
||||
// theme: {
|
||||
// globalName: 'uniform',
|
||||
// globalColorParams: { value: 0xff0000 }
|
||||
// }
|
||||
// },
|
||||
// label: 'my structure'
|
||||
// });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
var enableDpoit = getParam('enable-dpoit', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
@@ -74,8 +76,10 @@
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
enableWboit: disableWboit ? false : void 0, // use default value if disable-wboit is not set
|
||||
enableWboit: (disableWboit || enableDpoit) ? false : void 0, // use default value if disable-wboit is not set
|
||||
enableDpoit: enableDpoit ? true : void 0,
|
||||
preferWebgl1: preferWebgl1,
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
}).then(viewer => {
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
@@ -60,6 +60,8 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const merged = merge(
|
||||
this.controls.behaviors.params,
|
||||
this.plugin.canvas3d!.reprCount
|
||||
|
||||
@@ -118,11 +118,13 @@ export class Mp4Controls extends PluginComponent {
|
||||
}
|
||||
|
||||
private init() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
|
||||
this.sync();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.canvas3d.resized, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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>
|
||||
@@ -260,7 +260,8 @@ namespace Camera {
|
||||
radius: 0,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
clipFar: true,
|
||||
minNear: 5,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -276,6 +277,7 @@ namespace Camera {
|
||||
radiusMax: number
|
||||
fog: number
|
||||
clipFar: boolean
|
||||
minNear: number
|
||||
}
|
||||
|
||||
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
|
||||
@@ -292,6 +294,7 @@ namespace Camera {
|
||||
if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
|
||||
if (typeof source.fog !== 'undefined') out.fog = source.fog;
|
||||
if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
|
||||
if (typeof source.minNear !== 'undefined') out.minNear = source.minNear;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -303,6 +306,7 @@ namespace Camera {
|
||||
&& a.radiusMax === b.radiusMax
|
||||
&& a.fog === b.fog
|
||||
&& a.clipFar === b.clipFar
|
||||
&& a.minNear === b.minNear
|
||||
&& Vec3.exactEquals(a.position, b.position)
|
||||
&& Vec3.exactEquals(a.up, b.up)
|
||||
&& Vec3.exactEquals(a.target, b.target);
|
||||
@@ -370,7 +374,7 @@ function updatePers(camera: Camera) {
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state;
|
||||
let { radius, radiusMax, mode, fog, clipFar, minNear } = camera.state;
|
||||
if (radius < 0.01) radius = 0.01;
|
||||
|
||||
const normalizedFar = clipFar ? radius : radiusMax;
|
||||
@@ -384,12 +388,12 @@ function updateClip(camera: Camera) {
|
||||
|
||||
if (mode === 'perspective') {
|
||||
// set at least to 5 to avoid slow sphere impostor rendering
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
near = Math.max(Math.min(radiusMax, minNear), near);
|
||||
far = Math.max(minNear, far);
|
||||
} else {
|
||||
// not too close to 0 as it causes issues with outline rendering
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
near = Math.max(Math.min(radiusMax, minNear), near);
|
||||
far = Math.max(minNear, far);
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -13,8 +13,8 @@ import { Camera, ICamera } from '../camera';
|
||||
import { Viewport } from './util';
|
||||
|
||||
export const StereoCameraParams = {
|
||||
eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
|
||||
focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
|
||||
eyeSeparation: PD.Numeric(0.062, { min: 0.02, max: 0.1, step: 0.001 }, { description: 'Distance between left and right camera.' }),
|
||||
focus: PD.Numeric(10, { min: 1, max: 20, step: 0.1 }, { description: 'Apparent object distance.' }),
|
||||
};
|
||||
export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
|
||||
export type StereoCameraProps = PD.Values<typeof StereoCameraParams>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
@@ -39,9 +40,10 @@ import { Helper } from './helper/helper';
|
||||
import { Passes } from './passes/passes';
|
||||
import { shallowEqual } from '../mol-util';
|
||||
import { MarkingParams } from './passes/marking';
|
||||
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item';
|
||||
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit, GraphicsRenderVariantsDpoit } from '../mol-gl/webgl/render-item';
|
||||
import { degToRad, radToDeg } from '../mol-math/misc';
|
||||
import { AssetManager } from '../mol-util/assets';
|
||||
import { deepClone } from '../mol-util/object';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
camera: PD.Group({
|
||||
@@ -63,6 +65,7 @@ export const Canvas3DParams = {
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
minNear: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }, { description: 'Note, may cause performance issues rendering impostors when set too small and cause issues with outline rendering when too close to 0.' }),
|
||||
}, { pivot: 'radius' }),
|
||||
viewport: PD.MappedStatic('canvas', {
|
||||
canvas: PD.Group({}),
|
||||
@@ -83,6 +86,7 @@ export const Canvas3DParams = {
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
@@ -116,6 +120,7 @@ interface Canvas3DContext {
|
||||
|
||||
namespace Canvas3DContext {
|
||||
export const DefaultAttribs = {
|
||||
failIfMajorPerformanceCaveat: false,
|
||||
/** true by default to avoid issues with Safari (Jan 2021) */
|
||||
antialias: true,
|
||||
/** true to support multiple Canvas3D objects with a single context */
|
||||
@@ -125,14 +130,19 @@ namespace Canvas3DContext {
|
||||
/** extra pixels to around target to check in case target is empty */
|
||||
pickPadding: 1,
|
||||
enableWboit: true,
|
||||
enableDpoit: false,
|
||||
preferWebGl1: false
|
||||
};
|
||||
export type Attribs = typeof DefaultAttribs
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, assetManager: AssetManager, attribs: Partial<Attribs> = {}): Canvas3DContext {
|
||||
const a = { ...DefaultAttribs, ...attribs };
|
||||
const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
|
||||
|
||||
if (a.enableWboit && a.enableDpoit) throw new Error('Multiple transparency methods not allowed.');
|
||||
|
||||
const { failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
|
||||
const gl = getGLContext(canvas, {
|
||||
failIfMajorPerformanceCaveat,
|
||||
antialias,
|
||||
preserveDrawingBuffer,
|
||||
alpha: true, // the renderer requires an alpha channel
|
||||
@@ -285,7 +295,7 @@ namespace Canvas3D {
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
|
||||
export function create({ webgl, input, passes, attribs, assetManager }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
|
||||
const p: Canvas3DProps = { ...deepClone(DefaultCanvas3DParams), ...deepClone(props) };
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
|
||||
@@ -302,8 +312,7 @@ namespace Canvas3D {
|
||||
let width = 128;
|
||||
let height = 128;
|
||||
updateViewport();
|
||||
|
||||
const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended);
|
||||
const scene = Scene.create(webgl, passes.draw.dpoitEnabled ? GraphicsRenderVariantsDpoit : (passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended));
|
||||
|
||||
function getSceneRadius() {
|
||||
return scene.boundingSphere.radius * p.sceneRadiusFactor;
|
||||
@@ -314,6 +323,7 @@ namespace Canvas3D {
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far,
|
||||
minNear: p.cameraClipping.minNear,
|
||||
fov: degToRad(p.camera.fov),
|
||||
}, { x, y, width, height }, { pixelScale: attribs.pixelScale });
|
||||
const stereoCamera = new StereoCamera(camera, p.camera.stereo.params);
|
||||
@@ -679,10 +689,11 @@ namespace Canvas3D {
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraClipping: { far: camera.state.clipFar, radius, minNear: camera.state.minNear },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
sceneRadiusFactor: p.sceneRadiusFactor,
|
||||
transparentBackground: p.transparentBackground,
|
||||
dpoitIterations: p.dpoitIterations,
|
||||
viewport: p.viewport,
|
||||
|
||||
postprocessing: { ...p.postprocessing },
|
||||
@@ -805,6 +816,9 @@ namespace Canvas3D {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far;
|
||||
}
|
||||
if (props.cameraClipping.minNear !== undefined && props.cameraClipping.minNear !== camera.state.minNear) {
|
||||
cameraState.minNear = props.cameraClipping.minNear;
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (getSceneRadius() / 100) * (100 - props.cameraClipping.radius);
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
@@ -817,9 +831,13 @@ namespace Canvas3D {
|
||||
|
||||
if (props.camera?.helper) helper.camera.setProps(props.camera.helper);
|
||||
if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset;
|
||||
if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo);
|
||||
if (props.camera?.stereo !== undefined) {
|
||||
Object.assign(p.camera.stereo, props.camera.stereo);
|
||||
stereoCamera.setProps(p.camera.stereo.params);
|
||||
}
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
|
||||
if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations;
|
||||
if (props.viewport !== undefined) {
|
||||
const doNotUpdate = p.viewport === props.viewport ||
|
||||
(p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params));
|
||||
@@ -855,7 +873,7 @@ namespace Canvas3D {
|
||||
}
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
|
||||
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, passes.draw.dpoitEnabled, props);
|
||||
},
|
||||
getRenderObjects(): GraphicsRenderObject[] {
|
||||
const renderObjects: GraphicsRenderObject[] = [];
|
||||
@@ -920,4 +938,4 @@ namespace Canvas3D {
|
||||
Viewport.set(controls.viewport, x, y, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
309
src/mol-canvas3d/passes/dpoit.ts
Normal file
309
src/mol-canvas3d/passes/dpoit.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* Adapted from https://github.com/tsherif/webgl2examples, The MIT License, Copyright © 2017 Tarek Sherif, Shuai Shao
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { quad_vert } from '../../mol-gl/shader/quad.vert';
|
||||
import { evaluateDpoit_frag } from '../../mol-gl/shader/evaluate-dpoit.frag';
|
||||
import { blendBackDpoit_frag } from '../../mol-gl/shader/blend-back-dpoit.frag';
|
||||
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../mol-gl/webgl/compat';
|
||||
|
||||
const BlendBackDpoitSchema = {
|
||||
...QuadSchema,
|
||||
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
};
|
||||
const BlendBackDpoitShaderCode = ShaderCode('blend-back-dpoit', quad_vert, blendBackDpoit_frag);
|
||||
type BlendBackDpoitRenderable = ComputeRenderable<Values<typeof BlendBackDpoitSchema>>
|
||||
|
||||
function getBlendBackDpoitRenderable(ctx: WebGLContext, dopitBlendBackTexture: Texture): BlendBackDpoitRenderable {
|
||||
const values: Values<typeof BlendBackDpoitSchema> = {
|
||||
...QuadValues,
|
||||
tDpoitBackColor: ValueCell.create(dopitBlendBackTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(dopitBlendBackTexture.getWidth(), dopitBlendBackTexture.getHeight())),
|
||||
};
|
||||
|
||||
const schema = { ...BlendBackDpoitSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', BlendBackDpoitShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
const EvaluateDpoitSchema = {
|
||||
...QuadSchema,
|
||||
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
};
|
||||
const EvaluateDpoitShaderCode = ShaderCode('evaluate-dpoit', quad_vert, evaluateDpoit_frag);
|
||||
type EvaluateDpoitRenderable = ComputeRenderable<Values<typeof EvaluateDpoitSchema>>
|
||||
|
||||
function getEvaluateDpoitRenderable(ctx: WebGLContext, dpoitFrontColorTexture: Texture): EvaluateDpoitRenderable {
|
||||
const values: Values<typeof EvaluateDpoitSchema> = {
|
||||
...QuadValues,
|
||||
tDpoitFrontColor: ValueCell.create(dpoitFrontColorTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(dpoitFrontColorTexture.getWidth(), dpoitFrontColorTexture.getHeight())),
|
||||
};
|
||||
|
||||
const schema = { ...EvaluateDpoitSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateDpoitShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
export class DpoitPass {
|
||||
private readonly DEPTH_CLEAR_VALUE = -99999.0; // NOTE same constant is set in shaders
|
||||
private readonly MAX_DEPTH = 1.0;
|
||||
private readonly MIN_DEPTH = 0.0;
|
||||
|
||||
private passCount = 0;
|
||||
private writeId: number;
|
||||
private readId: number;
|
||||
|
||||
private readonly blendBackRenderable: BlendBackDpoitRenderable;
|
||||
private readonly renderable: EvaluateDpoitRenderable;
|
||||
|
||||
private readonly depthFramebuffers: Framebuffer[];
|
||||
private readonly colorFramebuffers: Framebuffer[];
|
||||
|
||||
private readonly depthTextures: Texture[];
|
||||
private readonly colorFrontTextures: Texture[];
|
||||
private readonly colorBackTextures: Texture[];
|
||||
|
||||
private _supported = false;
|
||||
get supported() {
|
||||
return this._supported;
|
||||
}
|
||||
|
||||
bind() {
|
||||
const { state, gl, extensions: { blendMinMax } } = this.webgl;
|
||||
|
||||
// initialize
|
||||
this.passCount = 0;
|
||||
|
||||
this.depthFramebuffers[0].bind();
|
||||
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[1].bind();
|
||||
state.clearColor(-this.MIN_DEPTH, this.MAX_DEPTH, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[0].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[1].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[0].bind();
|
||||
state.blendEquation(blendMinMax!.MAX);
|
||||
state.depthMask(false);
|
||||
|
||||
return {
|
||||
depth: this.depthTextures[1],
|
||||
frontColor: this.colorFrontTextures[1],
|
||||
backColor: this.colorBackTextures[1]
|
||||
};
|
||||
}
|
||||
|
||||
bindDualDepthPeeling() {
|
||||
const { state, gl, extensions: { blendMinMax } } = this.webgl;
|
||||
|
||||
this.readId = this.passCount % 2;
|
||||
this.writeId = 1 - this.readId; // ping-pong: 0 or 1
|
||||
|
||||
this.passCount += 1; // increment for next pass
|
||||
|
||||
this.depthFramebuffers[this.writeId].bind();
|
||||
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[this.writeId].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[this.writeId].bind();
|
||||
state.blendEquation(blendMinMax!.MAX);
|
||||
state.depthMask(false);
|
||||
|
||||
return {
|
||||
depth: this.depthTextures[this.readId],
|
||||
frontColor: this.colorFrontTextures[this.readId],
|
||||
backColor: this.colorBackTextures[this.readId]
|
||||
};
|
||||
}
|
||||
|
||||
renderBlendBack() {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.renderBlendBack');
|
||||
const { state, gl } = this.webgl;
|
||||
|
||||
state.blendEquation(gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ValueCell.update(this.blendBackRenderable.values.tDpoitBackColor, this.colorBackTextures[this.writeId]);
|
||||
|
||||
this.blendBackRenderable.update();
|
||||
this.blendBackRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.renderBlendBack');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.render');
|
||||
const { state, gl } = this.webgl;
|
||||
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ValueCell.update(this.renderable.values.tDpoitFrontColor, this.colorFrontTextures[this.writeId]);
|
||||
|
||||
this.renderable.update();
|
||||
this.renderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.render');
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
if (width !== w || height !== h) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
this.depthTextures[i].define(width, height);
|
||||
this.colorFrontTextures[i].define(width, height);
|
||||
this.colorBackTextures[i].define(width, height);
|
||||
}
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.blendBackRenderable.values.uTexSize, Vec2.set(this.blendBackRenderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this._supported) this._init();
|
||||
}
|
||||
|
||||
private _init() {
|
||||
const { extensions: { drawBuffers } } = this.webgl;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// depth
|
||||
this.depthFramebuffers[i].bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1,
|
||||
drawBuffers!.COLOR_ATTACHMENT2
|
||||
]);
|
||||
|
||||
this.colorFrontTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color0');
|
||||
this.colorBackTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color1');
|
||||
this.depthTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color2');
|
||||
|
||||
// color
|
||||
this.colorFramebuffers[i].bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1
|
||||
]);
|
||||
|
||||
this.colorFrontTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color0');
|
||||
this.colorBackTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color1');
|
||||
}
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, blendMinMax } } = webgl;
|
||||
if (!textureFloat || !colorBufferFloat || !drawBuffers || !blendMinMax) {
|
||||
if (isDebugMode) {
|
||||
const missing: string[] = [];
|
||||
if (!textureFloat) missing.push('textureFloat');
|
||||
if (!colorBufferFloat) missing.push('colorBufferFloat');
|
||||
if (!drawBuffers) missing.push('drawBuffers');
|
||||
if (!blendMinMax) missing.push('blendMinMax');
|
||||
console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!DpoitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
|
||||
|
||||
// textures
|
||||
|
||||
if (isWebGL2(webgl.gl)) {
|
||||
this.depthTextures = [
|
||||
resources.texture('image-float32', 'rg', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rg', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorFrontTextures = colorBufferHalfFloat && textureHalfFloat ? [
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
] : [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorBackTextures = colorBufferHalfFloat && textureHalfFloat ? [
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
] : [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
} else {
|
||||
// in webgl1 drawbuffers must be in the same format for some reason
|
||||
|
||||
this.depthTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorFrontTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorBackTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
}
|
||||
|
||||
this.depthTextures[0].define(width, height);
|
||||
this.depthTextures[1].define(width, height);
|
||||
|
||||
this.colorFrontTextures[0].define(width, height);
|
||||
this.colorFrontTextures[1].define(width, height);
|
||||
|
||||
this.colorBackTextures[0].define(width, height);
|
||||
this.colorBackTextures[1].define(width, height);
|
||||
|
||||
// framebuffers
|
||||
|
||||
this.depthFramebuffers = [resources.framebuffer(), resources.framebuffer()];
|
||||
this.colorFramebuffers = [resources.framebuffer(), resources.framebuffer()];
|
||||
|
||||
// renderables
|
||||
|
||||
this.blendBackRenderable = getBlendBackDpoitRenderable(webgl, this.colorBackTextures[0]);
|
||||
this.renderable = getEvaluateDpoitRenderable(webgl, this.colorFrontTextures[0]);
|
||||
|
||||
this._supported = true;
|
||||
this._init();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -17,6 +18,7 @@ import { Helper } from '../helper/helper';
|
||||
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
import { WboitPass } from './wboit';
|
||||
import { DpoitPass } from './dpoit';
|
||||
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
|
||||
import { MarkingPass, MarkingProps } from './marking';
|
||||
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
|
||||
@@ -27,6 +29,7 @@ type Props = {
|
||||
postprocessing: PostprocessingProps;
|
||||
marking: MarkingProps;
|
||||
transparentBackground: boolean;
|
||||
dpoitIterations: number;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
@@ -52,6 +55,7 @@ export class DrawPass {
|
||||
private copyFboPostprocessing: CopyRenderable;
|
||||
|
||||
private readonly wboit: WboitPass | undefined;
|
||||
private readonly dpoit: DpoitPass | undefined;
|
||||
private readonly marking: MarkingPass;
|
||||
readonly postprocessing: PostprocessingPass;
|
||||
private readonly antialiasing: AntialiasingPass;
|
||||
@@ -60,9 +64,12 @@ export class DrawPass {
|
||||
return !!this.wboit?.supported;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean) {
|
||||
const { extensions, resources, isWebGL2 } = webgl;
|
||||
get dpoitEnabled() {
|
||||
return !!this.dpoit?.supported;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean, enableDpoit: boolean) {
|
||||
const { extensions, resources, isWebGL2 } = webgl;
|
||||
this.drawTarget = createNullRenderTarget(webgl.gl);
|
||||
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
|
||||
this.packedDepth = !extensions.depthTexture;
|
||||
@@ -78,6 +85,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
|
||||
this.dpoit = enableDpoit ? new DpoitPass(webgl, width, height) : undefined;
|
||||
this.marking = new MarkingPass(webgl, width, height);
|
||||
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
|
||||
this.antialiasing = new AntialiasingPass(webgl, this);
|
||||
@@ -88,6 +96,7 @@ export class DrawPass {
|
||||
|
||||
reset() {
|
||||
this.wboit?.reset();
|
||||
this.dpoit?.reset();
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -111,12 +120,70 @@ export class DrawPass {
|
||||
this.wboit.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.dpoit?.supported) {
|
||||
this.dpoit.setSize(width, height);
|
||||
}
|
||||
|
||||
this.marking.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.antialiasing.setSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
if (!this.dpoit?.supported) throw new Error('expected dpoit to be supported');
|
||||
|
||||
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
renderer.clear(true);
|
||||
|
||||
// render opaque primitives
|
||||
if (scene.hasOpaque) {
|
||||
renderer.renderDpoitOpaque(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
if (PostprocessingPass.isEnabled(postprocessingProps)) {
|
||||
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
|
||||
this.depthTargetTransparent.bind();
|
||||
renderer.clearDepth(true);
|
||||
if (scene.opacityAverage < 1) {
|
||||
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
|
||||
}
|
||||
}
|
||||
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
|
||||
}
|
||||
|
||||
this.depthTextureOpaque.detachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
|
||||
// render transparent primitives
|
||||
if (scene.opacityAverage < 1) {
|
||||
const target = PostprocessingPass.isEnabled(postprocessingProps)
|
||||
? this.postprocessing.target : this.colorTarget;
|
||||
|
||||
const dpoitTextures = this.dpoit.bind();
|
||||
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.layer');
|
||||
const dpoitTextures = this.dpoit.bindDualDepthPeeling();
|
||||
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
|
||||
|
||||
target.bind();
|
||||
this.dpoit.renderBlendBack();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.layer');
|
||||
}
|
||||
|
||||
// evaluate dpoit
|
||||
target.bind();
|
||||
this.dpoit.render();
|
||||
}
|
||||
|
||||
// render transparent volumes
|
||||
if (scene.volumes.renderables.length > 0) {
|
||||
renderer.renderDpoitVolume(scene.volumes, camera, this.depthTextureOpaque);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
|
||||
|
||||
@@ -254,13 +321,15 @@ export class DrawPass {
|
||||
|
||||
if (this.wboitEnabled) {
|
||||
this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
|
||||
} else if (this.dpoitEnabled) {
|
||||
this._renderDpoit(renderer, camera, scene, props.dpoitIterations, transparentBackground, props.postprocessing);
|
||||
} else {
|
||||
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
|
||||
}
|
||||
|
||||
const target = postprocessingEnabled
|
||||
? this.postprocessing.target
|
||||
: !toDrawingBuffer || volumeRendering || this.wboitEnabled
|
||||
: !toDrawingBuffer || volumeRendering || this.wboitEnabled || this.dpoitEnabled
|
||||
? this.colorTarget
|
||||
: this.drawTarget;
|
||||
|
||||
@@ -303,7 +372,7 @@ export class DrawPass {
|
||||
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
|
||||
if (postprocessingEnabled) {
|
||||
this.copyFboPostprocessing.render();
|
||||
} else if (volumeRendering || this.wboitEnabled) {
|
||||
} else if (volumeRendering || this.wboitEnabled || this.dpoitEnabled) {
|
||||
this.copyFboTarget.render();
|
||||
}
|
||||
}
|
||||
@@ -323,8 +392,12 @@ export class DrawPass {
|
||||
renderer.setPixelRatio(this.webgl.pixelRatio);
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
if (isTimingMode) this.webgl.timer.mark('StereoCamera.left');
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.left');
|
||||
if (isTimingMode) this.webgl.timer.mark('StereoCamera.right');
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.right');
|
||||
} else {
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
}
|
||||
@@ -339,4 +412,4 @@ export class DrawPass {
|
||||
}
|
||||
return this.colorTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { AssetManager } from '../../mol-util/assets';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
marking: PD.Group(MarkingParams),
|
||||
@@ -48,10 +49,10 @@ export class ImagePass {
|
||||
get width() { return this._width; }
|
||||
get height() { return this._height; }
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, enableDpoit: boolean, props: Partial<ImageProps>) {
|
||||
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
|
||||
|
||||
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit);
|
||||
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit, enableDpoit);
|
||||
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
|
||||
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ type Props = {
|
||||
postprocessing: PostprocessingProps
|
||||
marking: MarkingProps
|
||||
transparentBackground: boolean;
|
||||
dpoitIterations: number;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
|
||||
@@ -15,16 +15,19 @@ export class Passes {
|
||||
readonly pick: PickPass;
|
||||
readonly multiSample: MultiSamplePass;
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean, enableDpoit: boolean }> = {}) {
|
||||
const { gl } = webgl;
|
||||
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
|
||||
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false, attribs.enableDpoit || false);
|
||||
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
|
||||
this.multiSample = new MultiSamplePass(webgl, this.draw);
|
||||
}
|
||||
|
||||
updateSize() {
|
||||
const { gl } = this.webgl;
|
||||
this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
// Avoid setting dimensions to 0x0 because it causes "empty textures are not allowed" error.
|
||||
const width = Math.max(gl.drawingBufferWidth, 2);
|
||||
const height = Math.max(gl.drawingBufferHeight, 2);
|
||||
this.draw.setSize(width, height);
|
||||
this.pick.syncSize();
|
||||
this.multiSample.syncSize();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ export class SmaaPass {
|
||||
state.viewport(x, y, width, height);
|
||||
state.scissor(x, y, width, height);
|
||||
|
||||
state.colorMask(true, true, true, true);
|
||||
state.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag';
|
||||
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../mol-gl/webgl/compat';
|
||||
import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
|
||||
|
||||
const EvaluateWboitSchema = {
|
||||
...QuadSchema,
|
||||
@@ -50,6 +52,7 @@ export class WboitPass {
|
||||
private readonly framebuffer: Framebuffer;
|
||||
private readonly textureA: Texture;
|
||||
private readonly textureB: Texture;
|
||||
private readonly depthRenderbuffer: Renderbuffer;
|
||||
|
||||
private _supported = false;
|
||||
get supported() {
|
||||
@@ -87,6 +90,7 @@ export class WboitPass {
|
||||
if (width !== w || height !== h) {
|
||||
this.textureA.define(width, height);
|
||||
this.textureB.define(width, height);
|
||||
this.depthRenderbuffer.setSize(width, height);
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
@@ -106,6 +110,8 @@ export class WboitPass {
|
||||
|
||||
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
|
||||
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
|
||||
|
||||
this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
@@ -128,7 +134,7 @@ export class WboitPass {
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!WboitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources } = webgl;
|
||||
const { resources, gl } = webgl;
|
||||
|
||||
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureA.define(width, height);
|
||||
@@ -136,6 +142,10 @@ export class WboitPass {
|
||||
this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureB.define(width, height);
|
||||
|
||||
this.depthRenderbuffer = isWebGL2(gl)
|
||||
? resources.renderbuffer('depth32f', 'depth', width, height)
|
||||
: resources.renderbuffer('depth16', 'depth', width, height);
|
||||
|
||||
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
|
||||
this.framebuffer = resources.framebuffer();
|
||||
|
||||
|
||||
@@ -387,6 +387,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
|
||||
const type = isInstanceType ? 'volumeInstance' : 'volume';
|
||||
if (isTimingMode) webgl.timer.markEnd('calcTextureMeshColorSmoothing');
|
||||
|
||||
// printTextureImage(readTexture(webgl, texture), { scale: 0.75 });
|
||||
|
||||
return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
|
||||
}
|
||||
|
||||
|
||||
@@ -53,17 +53,17 @@ describe('renderer', () => {
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(9);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 6 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
|
||||
scene.remove(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
|
||||
ctx.resources.destroy();
|
||||
expect(ctx.stats.resourceCounts.program).toBe(0);
|
||||
|
||||
@@ -197,7 +197,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
gl.finish();
|
||||
if (isTimingMode) ctx.timer.markEnd('createHistogramPyramid');
|
||||
|
||||
// printTexture(ctx, pyramidTex, 2)
|
||||
// printTextureImage(readTexture(ctx, pyramidTex), { scale: 0.75 });
|
||||
|
||||
//
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
|
||||
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim);
|
||||
// console.log('volumeData', volumeData);
|
||||
// console.log('at', readTexture(ctx, activeVoxelsTex));
|
||||
// printTextureImage(readTexture(ctx, activeVoxelsTex), { scale: 0.75 });
|
||||
|
||||
gl.finish();
|
||||
if (isTimingMode) ctx.timer.markEnd('calcActiveVoxels');
|
||||
|
||||
@@ -199,6 +199,10 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
gl.finish();
|
||||
if (isTimingMode) ctx.timer.markEnd('createIsosurfaceBuffers');
|
||||
|
||||
// printTextureImage(readTexture(ctx, vertexTexture, new Float32Array(width * height * 4)), { scale: 0.75 });
|
||||
// printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75 });
|
||||
// printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75 });
|
||||
|
||||
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
|
||||
}
|
||||
|
||||
|
||||
@@ -75,9 +75,9 @@ export function getSharedCopyRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
const ReadTextureName = 'read-texture';
|
||||
const ReadAlphaTextureName = 'read-alpha-texture';
|
||||
|
||||
export function readTexture(ctx: WebGLContext, texture: Texture) {
|
||||
export function readTexture<T extends Uint8Array | Float32Array | Int32Array = Uint8Array>(ctx: WebGLContext, texture: Texture, array?: T) {
|
||||
const { gl, resources } = ctx;
|
||||
if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
|
||||
if (!array && texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
|
||||
|
||||
if (!ctx.namedFramebuffers[ReadTextureName]) {
|
||||
ctx.namedFramebuffers[ReadTextureName] = resources.framebuffer();
|
||||
@@ -86,7 +86,7 @@ export function readTexture(ctx: WebGLContext, texture: Texture) {
|
||||
|
||||
const width = texture.getWidth();
|
||||
const height = texture.getHeight();
|
||||
const array = new Uint8Array(width * height * 4);
|
||||
if (!array) array = new Uint8Array(width * height * 4) as T;
|
||||
framebuffer.bind();
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
ctx.readPixels(0, 0, width, height, array);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { ValueCell } from '../../mol-util';
|
||||
@@ -167,6 +168,11 @@ export type GlobalUniformValues = Values<GlobalUniformSchema>
|
||||
|
||||
export const GlobalTextureSchema = {
|
||||
tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
|
||||
|
||||
// dpoit
|
||||
tDpoitDepth: TextureSpec('texture', 'rg', 'float', 'nearest'),
|
||||
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest')
|
||||
} as const;
|
||||
export type GlobalTextureSchema = typeof GlobalTextureSchema
|
||||
export type GlobalTextureValues = Values<GlobalTextureSchema>
|
||||
@@ -236,7 +242,7 @@ export const TransparencySchema = {
|
||||
uTransparencyGridDim: UniformSpec('v3'),
|
||||
uTransparencyGridTransform: UniformSpec('v4'),
|
||||
tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
|
||||
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
|
||||
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance'])
|
||||
} as const;
|
||||
export type TransparencySchema = typeof TransparencySchema
|
||||
export type TransparencyValues = Values<TransparencySchema>
|
||||
@@ -327,4 +333,4 @@ export const BaseSchema = {
|
||||
invariantBoundingSphere: ValueSpec('sphere'),
|
||||
} as const;
|
||||
export type BaseSchema = typeof BaseSchema
|
||||
export type BaseValues = Values<BaseSchema>
|
||||
export type BaseValues = Values<BaseSchema>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -8,6 +8,7 @@ import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { TextureFilter } from '../webgl/texture';
|
||||
import { arrayMinMax } from '../../mol-util/array';
|
||||
|
||||
export function calculateTextureInfo(n: number, itemSize: number) {
|
||||
n = Math.max(n, 2); // observed issues with 1 pixel textures
|
||||
@@ -42,7 +43,8 @@ export function createTextureImage<T extends Uint8Array | Float32Array>(n: numbe
|
||||
const DefaultPrintImageOptions = {
|
||||
scale: 1,
|
||||
pixelated: false,
|
||||
id: 'molstar.debug.image'
|
||||
id: 'molstar.debug.image',
|
||||
normalize: false,
|
||||
};
|
||||
export type PrintImageOptions = typeof DefaultPrintImageOptions
|
||||
|
||||
@@ -58,7 +60,17 @@ export function printTextureImage(textureImage: TextureImage<any>, options: Part
|
||||
}
|
||||
}
|
||||
} else if (itemSize === 4) {
|
||||
data.set(array);
|
||||
if (options.normalize) {
|
||||
const [min, max] = arrayMinMax(array);
|
||||
for (let i = 0, il = width * height * 4; i < il; i += 4) {
|
||||
data[i] = ((array[i] - min) / (max - min)) * 255;
|
||||
data[i + 1] = ((array[i + 1] - min) / (max - min)) * 255;
|
||||
data[i + 2] = ((array[i + 2] - min) / (max - min)) * 255;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
} else {
|
||||
data.set(array);
|
||||
}
|
||||
} else {
|
||||
console.warn(`itemSize '${itemSize}' not supported`);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { Viewport } from '../mol-canvas3d/camera/util';
|
||||
@@ -70,6 +71,9 @@ interface Renderer {
|
||||
renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderDpoitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderDpoitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => void
|
||||
renderDpoitVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
|
||||
setProps: (props: Partial<RendererProps>) => void
|
||||
setViewport: (x: number, y: number, width: number, height: number) => void
|
||||
@@ -141,7 +145,7 @@ namespace Renderer {
|
||||
const enum Flag {
|
||||
None = 0,
|
||||
BlendedFront = 1,
|
||||
BlendedBack = 2
|
||||
BlendedBack = 2,
|
||||
}
|
||||
|
||||
const enum Mask {
|
||||
@@ -268,7 +272,7 @@ namespace Renderer {
|
||||
}
|
||||
|
||||
if (r.values.dGeometryType.ref.value === 'directVolume') {
|
||||
if (variant !== 'colorWboit' && variant !== 'colorBlended') {
|
||||
if (variant !== 'colorDpoit' && variant !== 'colorWboit' && variant !== 'colorBlended') {
|
||||
return; // only color supported
|
||||
}
|
||||
|
||||
@@ -602,6 +606,71 @@ namespace Renderer {
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent');
|
||||
};
|
||||
|
||||
const renderDpoitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitOpaque');
|
||||
state.disable(gl.BLEND);
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, Mask.Opaque, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// 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.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitOpaque');
|
||||
};
|
||||
|
||||
const renderDpoitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => {
|
||||
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitTransparent');
|
||||
|
||||
state.enable(gl.BLEND);
|
||||
|
||||
arrayMapUpsert(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor);
|
||||
|
||||
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
|
||||
|
||||
const { renderables } = group;
|
||||
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// 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.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitTransparent');
|
||||
};
|
||||
|
||||
const renderDpoitVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitVolume');
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
state.enable(gl.BLEND);
|
||||
|
||||
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
if (r.values.dGeometryType.ref.value === 'directVolume') {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitVolume');
|
||||
};
|
||||
|
||||
return {
|
||||
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
@@ -645,6 +714,9 @@ namespace Renderer {
|
||||
renderBlendedVolume,
|
||||
renderWboitOpaque,
|
||||
renderWboitTransparent,
|
||||
renderDpoitOpaque,
|
||||
renderDpoitTransparent,
|
||||
renderDpoitVolume,
|
||||
|
||||
setProps: (props: Partial<RendererProps>) => {
|
||||
if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
|
||||
@@ -762,4 +834,4 @@ namespace Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
export { Renderer };
|
||||
export { Renderer };
|
||||
|
||||
@@ -45,8 +45,8 @@ function calculateBoundingSphere(renderables: GraphicsRenderable[], boundingSphe
|
||||
}
|
||||
|
||||
function renderableSort(a: GraphicsRenderable, b: GraphicsRenderable) {
|
||||
const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
|
||||
const drawProgramIdB = (b.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
|
||||
const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit') || a.getProgram('colorDpoit')).id;
|
||||
const drawProgramIdB = (b.getProgram('colorBlended') || b.getProgram('colorWboit') || b.getProgram('colorDpoit')).id;
|
||||
const materialIdA = a.materialId;
|
||||
const materialIdB = b.materialId;
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-t
|
||||
import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl';
|
||||
import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl';
|
||||
import { wboit_write } from './shader/chunks/wboit-write.glsl';
|
||||
import { dpoit_write } from './shader/chunks/dpoit-write.glsl';
|
||||
|
||||
const ShaderChunks: { [k: string]: string } = {
|
||||
apply_fog,
|
||||
@@ -99,7 +100,8 @@ const ShaderChunks: { [k: string]: string } = {
|
||||
texture3d_from_1d_trilinear,
|
||||
texture3d_from_2d_linear,
|
||||
texture3d_from_2d_nearest,
|
||||
wboit_write
|
||||
wboit_write,
|
||||
dpoit_write
|
||||
};
|
||||
|
||||
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm;
|
||||
|
||||
20
src/mol-gl/shader/blend-back-dpoit.frag.ts
Normal file
20
src/mol-gl/shader/blend-back-dpoit.frag.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const blendBackDpoit_frag = `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tDpoitBackColor;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
void main() {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
gl_FragColor = texture2D(tDpoitBackColor, coords);
|
||||
if (gl_FragColor.a == 0.0) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -12,8 +12,19 @@ if (!uTransparentBackground) {
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
|
||||
}
|
||||
} else {
|
||||
// pre-multiplied alpha expected for transparent background
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
if (gl_FragColor.a < 1.0) {
|
||||
// transparent objects are blended with background color
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
// opaque objects need to be pre-multiplied alpha
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
}
|
||||
#else
|
||||
// pre-multiplied alpha expected for transparent background
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
@@ -57,6 +57,7 @@ export const apply_light_color = `
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
outgoingLight = clamp(outgoingLight, 0.0, 1.0); // prevents black artifacts on specular highlight with transparent background
|
||||
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
#endif
|
||||
|
||||
@@ -86,7 +86,7 @@ export const assign_material_color = `
|
||||
// apply per-group transparency
|
||||
#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color))
|
||||
float ta = 1.0 - vTransparency;
|
||||
if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better
|
||||
if (vTransparency < 0.09) ta = 1.0; // hard cutoff looks better
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (ta < uPickingAlphaThreshold)
|
||||
|
||||
@@ -39,6 +39,12 @@ uniform int uMarkingType;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
#define MAX_DPOIT_DEPTH 99999.0 // NOTE constant also set in TypeScript
|
||||
uniform sampler2D tDpoitDepth;
|
||||
uniform sampler2D tDpoitFrontColor;
|
||||
#endif
|
||||
|
||||
varying vec3 vModelPosition;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const common = `
|
||||
// TODO find a better place for these convenience defines
|
||||
|
||||
#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit)
|
||||
#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) || defined(dRenderVariant_colorDpoit)
|
||||
#define dRenderVariant_color
|
||||
#endif
|
||||
|
||||
|
||||
70
src/mol-gl/shader/chunks/dpoit-write.glsl.ts
Normal file
70
src/mol-gl/shader/chunks/dpoit-write.glsl.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const dpoit_write = `
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
if (uRenderMask == MaskOpaque) {
|
||||
if (preFogAlpha < 1.0) {
|
||||
discard;
|
||||
}
|
||||
} else if (uRenderMask == MaskTransparent) {
|
||||
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
|
||||
vec2 coords = gl_FragCoord.xy / uDrawingBufferSize;
|
||||
if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(coords) || fragmentDepth > 0.99)) {
|
||||
#ifdef dTransparentBackfaces_off
|
||||
if (interior) discard;
|
||||
#endif
|
||||
|
||||
// adapted from https://github.com/tsherif/webgl2examples
|
||||
// The MIT License, Copyright 2017 Tarek Sherif, Shuai Shao
|
||||
|
||||
vec2 lastDepth = texture2D(tDpoitDepth, coords).rg;
|
||||
vec4 lastFrontColor = texture2D(tDpoitFrontColor, coords);
|
||||
|
||||
vec4 fragColor = gl_FragColor;
|
||||
|
||||
// depth value always increases
|
||||
// so we can use MAX blend equation
|
||||
gl_FragData[2].rg = vec2(-MAX_DPOIT_DEPTH);
|
||||
|
||||
// front color always increases
|
||||
// so we can use MAX blend equation
|
||||
gl_FragColor = lastFrontColor;
|
||||
|
||||
// back color is separately blend afterwards each pass
|
||||
gl_FragData[1] = vec4(0.0);
|
||||
|
||||
float nearestDepth = -lastDepth.x;
|
||||
float furthestDepth = lastDepth.y;
|
||||
float alphaMultiplier = 1.0 - lastFrontColor.a;
|
||||
|
||||
if (fragmentDepth < nearestDepth || fragmentDepth > furthestDepth) {
|
||||
// Skip this depth since it's been peeled.
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragmentDepth > nearestDepth && fragmentDepth < furthestDepth) {
|
||||
// This needs to be peeled.
|
||||
// The ones remaining after MAX blended for
|
||||
// all need-to-peel will be peeled next pass.
|
||||
gl_FragData[2].rg = vec2(-fragmentDepth, fragmentDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
// write to back and front color buffer
|
||||
if (fragmentDepth == nearestDepth) {
|
||||
gl_FragColor.rgb += fragColor.rgb * fragColor.a * alphaMultiplier;
|
||||
gl_FragColor.a = 1.0 - alphaMultiplier * (1.0 - fragColor.a);
|
||||
} else {
|
||||
gl_FragData[1] += fragColor;
|
||||
}
|
||||
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
`;
|
||||
@@ -109,14 +109,14 @@ void main() {
|
||||
|
||||
vec3 vViewPosition = vModelPosition + intersection.x * rayDir;
|
||||
vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz;
|
||||
gl_FragDepthEXT = calcDepth(vViewPosition);
|
||||
float fragmentDepth = calcDepth(vViewPosition);
|
||||
|
||||
if (fragmentDepth < 0.0) discard;
|
||||
if (fragmentDepth > 1.0) discard;
|
||||
|
||||
gl_FragDepthEXT = fragmentDepth;
|
||||
|
||||
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
|
||||
|
||||
if (gl_FragDepthEXT < 0.0) discard;
|
||||
if (gl_FragDepthEXT > 1.0) discard;
|
||||
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -142,6 +142,7 @@ void main() {
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -356,4 +356,4 @@ void main() {
|
||||
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
|
||||
#include wboit_write
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
17
src/mol-gl/shader/evaluate-dpoit.frag.ts
Normal file
17
src/mol-gl/shader/evaluate-dpoit.frag.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const evaluateDpoit_frag = `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tDpoitFrontColor;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
void main() {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
gl_FragColor = texture2D(tDpoitFrontColor, coords);
|
||||
}
|
||||
`;
|
||||
@@ -159,6 +159,7 @@ void main() {
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -39,6 +39,7 @@ void main(){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -62,6 +62,7 @@ void main() {
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -55,6 +55,7 @@ void main(){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -70,17 +70,17 @@ void main(void){
|
||||
}
|
||||
|
||||
vec3 vViewPosition = cameraPos;
|
||||
gl_FragDepthEXT = calcDepth(vViewPosition);
|
||||
if (!flag && gl_FragDepthEXT >= 0.0) {
|
||||
gl_FragDepthEXT = 0.0 + (0.0000001 / vRadius);
|
||||
float fragmentDepth = calcDepth(vViewPosition);
|
||||
if (!flag && fragmentDepth >= 0.0) {
|
||||
fragmentDepth = 0.0 + (0.0000001 / vRadius);
|
||||
}
|
||||
|
||||
if (fragmentDepth < 0.0) discard;
|
||||
if (fragmentDepth > 1.0) discard;
|
||||
|
||||
gl_FragDepthEXT = fragmentDepth;
|
||||
|
||||
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
|
||||
|
||||
if (gl_FragDepthEXT < 0.0) discard;
|
||||
if (gl_FragDepthEXT > 1.0) discard;
|
||||
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -105,6 +105,7 @@ void main(void){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -83,6 +83,7 @@ void main(){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer';
|
||||
@@ -49,11 +50,12 @@ export interface RenderItem<T extends string> {
|
||||
|
||||
//
|
||||
|
||||
const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', pick: '', depth: '', marking: '' };
|
||||
const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', colorDpoit: '', pick: '', depth: '', marking: '' };
|
||||
export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant
|
||||
export const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[];
|
||||
export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => v !== 'colorWboit');
|
||||
export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => v !== 'colorBlended');
|
||||
export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorDpoit'].includes(v));
|
||||
export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => !['colorBlended', 'colorDpoit'].includes(v));
|
||||
export const GraphicsRenderVariantsDpoit = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorBlended'].includes(v));
|
||||
|
||||
const ComputeRenderVariant = { compute: '' };
|
||||
export type ComputeRenderVariant = keyof typeof ComputeRenderVariant
|
||||
@@ -367,4 +369,4 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from './context';
|
||||
@@ -31,7 +32,7 @@ export type TextureKindValue = {
|
||||
export type TextureValueType = ValueOf<TextureKindValue>
|
||||
export type TextureKind = keyof TextureKindValue
|
||||
export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int'
|
||||
export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
|
||||
export type TextureFormat = 'alpha' | 'rg' | 'rgb' | 'rgba' | 'depth'
|
||||
/** Numbers are shortcuts for color attachment */
|
||||
export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
export type TextureFilter = 'nearest' | 'linear'
|
||||
@@ -63,6 +64,10 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T
|
||||
case 'rgb':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
|
||||
return gl.RGB;
|
||||
case 'rg':
|
||||
if (isWebGL2(gl) && type === 'float') return gl.RG;
|
||||
else if (isWebGL2(gl) && type === 'int') return gl.RG_INTEGER;
|
||||
else throw new Error('texture format "rg" requires webgl2 and type "float" or int"');
|
||||
case 'rgba':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER;
|
||||
return gl.RGBA;
|
||||
@@ -80,6 +85,13 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
|
||||
case 'fp16': return gl.R16F;
|
||||
case 'int': return gl.R32I;
|
||||
}
|
||||
case 'rg':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RG;
|
||||
case 'float': return gl.RG32F;
|
||||
case 'fp16': return gl.RG16F;
|
||||
case 'int': return gl.RG32I;
|
||||
}
|
||||
case 'rgb':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RGB;
|
||||
@@ -112,6 +124,7 @@ function getByteCount(format: TextureFormat, type: TextureType, width: number, h
|
||||
function getFormatSize(format: TextureFormat) {
|
||||
switch (format) {
|
||||
case 'alpha': return 1;
|
||||
case 'rg': return 2;
|
||||
case 'rgb': return 3;
|
||||
case 'rgba': return 4;
|
||||
case 'depth': return 4;
|
||||
|
||||
@@ -264,28 +264,35 @@ export namespace ArrayEncoding {
|
||||
return false;
|
||||
}
|
||||
|
||||
function packingSize(data: Int32Array, upperLimit: number) {
|
||||
function packingSizeUnsigned(data: Int32Array, upperLimit: number) {
|
||||
let size = 0;
|
||||
for (let i = 0, n = data.length; i < n; i++) {
|
||||
size += (data[i] / upperLimit) | 0;
|
||||
}
|
||||
size += data.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
function packingSizeSigned(data: Int32Array, upperLimit: number) {
|
||||
const lowerLimit = -upperLimit - 1;
|
||||
let size = 0;
|
||||
for (let i = 0, n = data.length; i < n; i++) {
|
||||
const value = data[i];
|
||||
if (value === 0) {
|
||||
size += 1;
|
||||
} else if (value > 0) {
|
||||
size += Math.ceil(value / upperLimit);
|
||||
if (value % upperLimit === 0) size += 1;
|
||||
if (value >= 0) {
|
||||
size += (value / upperLimit) | 0;
|
||||
} else {
|
||||
size += Math.ceil(value / lowerLimit);
|
||||
if (value % lowerLimit === 0) size += 1;
|
||||
size += (value / lowerLimit) | 0;
|
||||
}
|
||||
}
|
||||
size += data.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
function determinePacking(data: Int32Array): { isSigned: boolean, size: number, bytesPerElement: number } {
|
||||
const signed = isSigned(data);
|
||||
const size8 = signed ? packingSize(data, 0x7F) : packingSize(data, 0xFF);
|
||||
const size16 = signed ? packingSize(data, 0x7FFF) : packingSize(data, 0xFFFF);
|
||||
const size8 = signed ? packingSizeSigned(data, 0x7F) : packingSizeUnsigned(data, 0xFF);
|
||||
const size16 = signed ? packingSizeSigned(data, 0x7FFF) : packingSizeUnsigned(data, 0xFFFF);
|
||||
|
||||
if (data.length * 4 < size16 * 2) {
|
||||
// 4 byte packing is the most effective
|
||||
|
||||
@@ -204,7 +204,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
render(texture, false);
|
||||
}
|
||||
|
||||
// printTexture(webgl, minDistTex, 0.75);
|
||||
// printTextureImage(readTexture(webgl, minDistTex), { scale: 0.75 });
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ function Vec3() {
|
||||
|
||||
namespace Vec3 {
|
||||
export function zero(): Vec3 {
|
||||
const out = [0.1, 0.0, 0.0];
|
||||
const out = [0.1, 0.0, 0.0]; // ensure backing array of type double
|
||||
out[0] = 0;
|
||||
return out as any;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ const DnaAtomIdsList = [
|
||||
/** Used to reduce false positives for atom name-based type guessing */
|
||||
const NonPolymerNames = new Set([
|
||||
'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP', // Mononucleotides
|
||||
'LIG'
|
||||
]);
|
||||
|
||||
const StandardComponents = (function () {
|
||||
|
||||
@@ -39,7 +39,7 @@ export function getAtomSiteTemplate(data: string, count: number) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
|
||||
export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
|
||||
const pdbx_PDB_model_num = CifField.ofStrings(sites.pdbx_PDB_model_num);
|
||||
const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
|
||||
const auth_seq_id = CifField.ofTokens(sites.auth_seq_id);
|
||||
@@ -67,21 +67,17 @@ export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in k
|
||||
const seqId = auth_seq_id.int(i);
|
||||
let atomId = auth_atom_id.str(i);
|
||||
|
||||
let asymIdChanged = false;
|
||||
|
||||
if (modelNum !== currModelNum) {
|
||||
asymIdCounts.clear();
|
||||
atomIdCounts.clear();
|
||||
currModelNum = modelNum;
|
||||
currAsymId = asymId;
|
||||
currSeqId = seqId;
|
||||
asymIdChanged = true;
|
||||
currLabelAsymId = asymId;
|
||||
} else if (currAsymId !== asymId) {
|
||||
atomIdCounts.clear();
|
||||
currAsymId = asymId;
|
||||
currSeqId = seqId;
|
||||
asymIdChanged = true;
|
||||
currLabelAsymId = asymId;
|
||||
} else if (currSeqId !== seqId) {
|
||||
atomIdCounts.clear();
|
||||
@@ -91,7 +87,7 @@ export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in k
|
||||
if (asymIdCounts.has(asymId)) {
|
||||
// only change the chains name if there are TER records
|
||||
// otherwise assume repeated chain name use is from interleaved chains
|
||||
if (hasTer && asymIdChanged) {
|
||||
if (terIndices.has(i)) {
|
||||
const asymIdCount = asymIdCounts.get(asymId)! + 1;
|
||||
asymIdCounts.set(asymId, asymIdCount);
|
||||
currLabelAsymId = `${asymId}_${asymIdCount}`;
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
|
||||
let modelNum = 0, modelStr = '';
|
||||
let conectRange: [number, number] | undefined = undefined;
|
||||
let hasTer = false;
|
||||
const terIndices = new Set<number>();
|
||||
|
||||
for (let i = 0, _i = lines.count; i < _i; i++) {
|
||||
let s = indices[2 * i], e = indices[2 * i + 1];
|
||||
@@ -164,7 +164,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
break;
|
||||
case 'T':
|
||||
if (substringStartsWith(data, s, e, 'TER')) {
|
||||
hasTer = true;
|
||||
terIndices.add(atomSite.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i));
|
||||
}
|
||||
|
||||
const atom_site = getAtomSite(atomSite, hasTer);
|
||||
const atom_site = getAtomSite(atomSite, terIndices);
|
||||
if (!isPdbqt) delete atom_site.partial_charge;
|
||||
|
||||
if (conectRange) {
|
||||
|
||||
@@ -65,7 +65,7 @@ export namespace ComponentBond {
|
||||
return e;
|
||||
}
|
||||
|
||||
const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount } = data;
|
||||
const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount, pdbx_ordinal } = data;
|
||||
|
||||
let entry = addEntry(comp_id.value(0)!);
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
@@ -74,6 +74,7 @@ export namespace ComponentBond {
|
||||
const nameB = atom_id_2.value(i)!;
|
||||
const order = value_order.value(i)!;
|
||||
const aromatic = pdbx_aromatic_flag.value(i) === 'y';
|
||||
const key = pdbx_ordinal.value(i);
|
||||
|
||||
if (entry.id !== id) {
|
||||
entry = addEntry(id);
|
||||
@@ -89,29 +90,29 @@ export namespace ComponentBond {
|
||||
case 'quad': ord = 4; break;
|
||||
}
|
||||
|
||||
entry.add(nameA, nameB, ord, flags);
|
||||
entry.add(nameA, nameB, ord, flags, key);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export class Entry {
|
||||
readonly map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
|
||||
readonly map: Map<string, Map<string, { order: number, flags: number, key: number }>> = new Map();
|
||||
|
||||
add(a: string, b: string, order: number, flags: number, swap = true) {
|
||||
add(a: string, b: string, order: number, flags: number, key: number, swap = true) {
|
||||
const e = this.map.get(a);
|
||||
if (e !== void 0) {
|
||||
const f = e.get(b);
|
||||
if (f === void 0) {
|
||||
e.set(b, { order, flags });
|
||||
e.set(b, { order, flags, key });
|
||||
}
|
||||
} else {
|
||||
const map = new Map<string, { order: number, flags: number }>();
|
||||
map.set(b, { order, flags });
|
||||
const map = new Map<string, { order: number, flags: number, key: number }>();
|
||||
map.set(b, { order, flags, key });
|
||||
this.map.set(a, map);
|
||||
}
|
||||
|
||||
if (swap) this.add(b, a, order, flags, false);
|
||||
if (swap) this.add(b, a, order, flags, key, false);
|
||||
}
|
||||
|
||||
constructor(public readonly id: string) { }
|
||||
|
||||
@@ -22,6 +22,8 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { InteractionFlag } from '../interactions/common';
|
||||
import { Unit } from '../../../mol-model/structure/structure';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { InteractionsSharedParams } from './shared';
|
||||
|
||||
function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) {
|
||||
if (!structure.hasAtomic) return Mesh.createEmpty(mesh);
|
||||
@@ -31,7 +33,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
const { contacts, unitsFeatures } = interactions;
|
||||
|
||||
const { edgeCount, edges } = contacts;
|
||||
const { sizeFactor } = props;
|
||||
const { sizeFactor, parentDisplay } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
@@ -70,14 +72,48 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
|
||||
if (child) {
|
||||
const b = edges[edgeIndex];
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) return true;
|
||||
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitA);
|
||||
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
|
||||
const eA = unitA.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) return true;
|
||||
if (parentDisplay === 'stub') {
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) return true;
|
||||
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitA);
|
||||
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
|
||||
const eA = unitA.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) return true;
|
||||
}
|
||||
} else if (parentDisplay === 'full' || parentDisplay === 'between') {
|
||||
let flagA = false;
|
||||
let flagB = false;
|
||||
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) {
|
||||
flagA = true;
|
||||
} else {
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitA);
|
||||
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
|
||||
const eA = unitA.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) flagA = true;
|
||||
}
|
||||
}
|
||||
|
||||
const childUnitB = child.unitMap.get(b.unitB);
|
||||
if (!childUnitB) {
|
||||
flagB = true;
|
||||
} else {
|
||||
const unitB = structure.unitMap.get(b.unitB);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitB);
|
||||
for (let i = offsets[b.indexB], il = offsets[b.indexB + 1]; i < il; ++i) {
|
||||
const eB = unitB.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitB.elements, eB)) flagB = true;
|
||||
}
|
||||
}
|
||||
|
||||
return parentDisplay === 'full' ? flagA && flagB : flagA === flagB;
|
||||
} else {
|
||||
assertUnreachable(parentDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +137,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
export const InteractionsInterUnitParams = {
|
||||
...ComplexMeshParams,
|
||||
...LinkCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
...InteractionsSharedParams,
|
||||
};
|
||||
export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams
|
||||
|
||||
@@ -121,7 +154,8 @@ export function InteractionsInterUnitVisual(materialId: number): ComplexVisual<I
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
newProps.radialSegments !== currentProps.radialSegments
|
||||
newProps.radialSegments !== currentProps.radialSegments ||
|
||||
newProps.parentDisplay !== currentProps.parentDisplay
|
||||
);
|
||||
|
||||
const interactionsHash = InteractionsProvider.get(newStructure).version;
|
||||
|
||||
@@ -22,6 +22,8 @@ import { Interactions } from '../interactions/interactions';
|
||||
import { InteractionFlag } from '../interactions/common';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { InteractionsSharedParams } from './shared';
|
||||
|
||||
async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
|
||||
@@ -38,7 +40,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
|
||||
const { x, y, z, members, offsets } = features;
|
||||
const { edgeCount, a, b, edgeProps: { flag } } = contacts;
|
||||
const { sizeFactor } = props;
|
||||
const { sizeFactor, parentDisplay } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
@@ -60,10 +62,31 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
if (flag[edgeIndex] === InteractionFlag.Filtered) return true;
|
||||
|
||||
if (childUnit) {
|
||||
const f = a[edgeIndex];
|
||||
for (let i = offsets[f], jl = offsets[f + 1]; i < jl; ++i) {
|
||||
const e = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, e)) return true;
|
||||
if (parentDisplay === 'stub') {
|
||||
const f = a[edgeIndex];
|
||||
for (let i = offsets[f], il = offsets[f + 1]; i < il; ++i) {
|
||||
const e = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, e)) return true;
|
||||
}
|
||||
} else if (parentDisplay === 'full' || parentDisplay === 'between') {
|
||||
let flagA = false;
|
||||
let flagB = false;
|
||||
|
||||
const fA = a[edgeIndex];
|
||||
for (let i = offsets[fA], il = offsets[fA + 1]; i < il; ++i) {
|
||||
const eA = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, eA)) flagA = true;
|
||||
}
|
||||
|
||||
const fB = b[edgeIndex];
|
||||
for (let i = offsets[fB], il = offsets[fB + 1]; i < il; ++i) {
|
||||
const eB = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, eB)) flagB = true;
|
||||
}
|
||||
|
||||
return parentDisplay === 'full' ? flagA && flagB : flagA === flagB;
|
||||
} else {
|
||||
assertUnreachable(parentDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +109,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
export const InteractionsIntraUnitParams = {
|
||||
...UnitsMeshParams,
|
||||
...LinkCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
...InteractionsSharedParams,
|
||||
};
|
||||
export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams
|
||||
|
||||
@@ -106,7 +126,8 @@ export function InteractionsIntraUnitVisual(materialId: number): UnitsVisual<Int
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
newProps.radialSegments !== currentProps.radialSegments
|
||||
newProps.radialSegments !== currentProps.radialSegments ||
|
||||
newProps.parentDisplay !== currentProps.parentDisplay
|
||||
);
|
||||
|
||||
const interactionsHash = InteractionsProvider.get(newStructureGroup.structure).version;
|
||||
|
||||
16
src/mol-model-props/computed/representations/shared.ts
Normal file
16
src/mol-model-props/computed/representations/shared.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export const InteractionsSharedParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
parentDisplay: PD.Select('stub', PD.arrayToOptions(['stub', 'full', 'between'] as const), { description: 'Only has an effect when "includeParent" is enabled. "Stub" shows just the child side of interactions to the parent. "Full" shows both sides of interactions to the parent. "Between" shows only interactions to the parent.' }),
|
||||
};
|
||||
export type InteractionsSharedParams = typeof InteractionsSharedParams
|
||||
@@ -12,6 +12,7 @@ import * as modifiers from './query/queries/modifiers';
|
||||
import * as filters from './query/queries/filters';
|
||||
import * as combinators from './query/queries/combinators';
|
||||
import * as internal from './query/queries/internal';
|
||||
import * as atomset from './query/queries/atom-set';
|
||||
import { Predicates as pred } from './query/predicates';
|
||||
|
||||
export const Queries = {
|
||||
@@ -20,7 +21,8 @@ export const Queries = {
|
||||
modifiers,
|
||||
combinators,
|
||||
pred,
|
||||
internal
|
||||
internal,
|
||||
atomset
|
||||
};
|
||||
|
||||
export { StructureSelection, StructureQuery };
|
||||
export { StructureSelection, StructureQuery };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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 { Structure, StructureElement, Unit } from '../structure';
|
||||
@@ -113,6 +114,7 @@ class QueryContextBondInfo<U extends Unit = Unit> {
|
||||
bIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex;
|
||||
type: BondType = BondType.Flag.None;
|
||||
order: number = 0;
|
||||
key: number = -1;
|
||||
|
||||
private testFn: QueryPredicate = defaultBondTest;
|
||||
|
||||
|
||||
36
src/mol-model/structure/query/queries/atom-set.ts
Normal file
36
src/mol-model/structure/query/queries/atom-set.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Koya Sakuma
|
||||
* Adapted from MolQL implemtation of atom-set.ts
|
||||
*
|
||||
* Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureQuery } from '../query';
|
||||
import { StructureSelection } from '../selection';
|
||||
import { getCurrentStructureProperties } from './filters';
|
||||
import { QueryContext, QueryFn } from '../context';
|
||||
|
||||
|
||||
export function atomCount(ctx: QueryContext) {
|
||||
return ctx.currentStructure.elementCount;
|
||||
}
|
||||
|
||||
|
||||
export function countQuery(query: StructureQuery) {
|
||||
return (ctx: QueryContext) => {
|
||||
const sel = query(ctx);
|
||||
return StructureSelection.structureCount(sel);
|
||||
};
|
||||
}
|
||||
|
||||
export function propertySet(prop: QueryFn<any>) {
|
||||
return (ctx: QueryContext) => {
|
||||
const set = new Set();
|
||||
return getCurrentStructureProperties(ctx, prop, set);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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 { SetUtils } from '../../../../mol-util/set';
|
||||
import { Unit } from '../../structure';
|
||||
import { QueryContext, QueryFn, QueryPredicate } from '../context';
|
||||
import { QueryContext, QueryFn } from '../context';
|
||||
import { StructureQuery } from '../query';
|
||||
import { StructureSelection } from '../selection';
|
||||
import { structureAreIntersecting } from '../utils/structure-set';
|
||||
@@ -16,7 +17,7 @@ import { Structure } from '../../structure/structure';
|
||||
import { StructureElement } from '../../structure/element';
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
|
||||
export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuery {
|
||||
export function pick(query: StructureQuery, pred: QueryFn<any>): StructureQuery {
|
||||
return ctx => {
|
||||
const sel = query(ctx);
|
||||
const ret = StructureSelection.LinearBuilder(ctx.inputStructure);
|
||||
@@ -50,9 +51,7 @@ export function first(query: StructureQuery): StructureQuery {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UnitTypeProperties { atomic?: QueryFn, coarse?: QueryFn }
|
||||
|
||||
export function getCurrentStructureProperties(ctx: QueryContext, props: UnitTypeProperties, set: Set<any>) {
|
||||
export function getCurrentStructureProperties(ctx: QueryContext, props: QueryFn<any>, set: Set<any>) {
|
||||
const { units } = ctx.currentStructure;
|
||||
const l = ctx.pushCurrentElement();
|
||||
|
||||
@@ -61,9 +60,9 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType
|
||||
l.unit = unit;
|
||||
const elements = unit.elements;
|
||||
|
||||
let fn;
|
||||
if (Unit.isAtomic(unit)) fn = props.atomic;
|
||||
else fn = props.coarse;
|
||||
const fn = props;
|
||||
// if (Unit.isAtomic(unit)) fn = props.atomic;
|
||||
// else fn = props.coarse;
|
||||
if (!fn) continue;
|
||||
|
||||
for (let j = 0, _j = elements.length; j < _j; j++) {
|
||||
@@ -77,7 +76,7 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType
|
||||
return set;
|
||||
}
|
||||
|
||||
function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: UnitTypeProperties) {
|
||||
function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: QueryFn<any>) {
|
||||
const set = new Set();
|
||||
|
||||
const sel = query(ctx);
|
||||
@@ -92,7 +91,7 @@ function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props:
|
||||
return set;
|
||||
}
|
||||
|
||||
export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: UnitTypeProperties): StructureQuery {
|
||||
export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: QueryFn<any>): StructureQuery {
|
||||
return ctx => {
|
||||
const sel = query(ctx);
|
||||
const propSet = getSelectionProperties(ctx, propertySource, props);
|
||||
@@ -102,7 +101,7 @@ export function withSameAtomProperties(query: StructureQuery, propertySource: St
|
||||
StructureSelection.forEach(sel, (s, i) => {
|
||||
ctx.currentStructure = s;
|
||||
const currentProps = getCurrentStructureProperties(ctx, props, new Set());
|
||||
if (SetUtils.isSuperset(currentProps, propSet)) {
|
||||
if (SetUtils.isSuperset(propSet, currentProps)) {
|
||||
ret.add(s);
|
||||
}
|
||||
|
||||
@@ -248,7 +247,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
|
||||
const inputUnit = input.unitMap.get(unit.id) as Unit.Atomic;
|
||||
|
||||
const { offset, b, edgeProps: { flags, order } } = inputUnit.bonds;
|
||||
const { offset, b, edgeProps: { flags, order, key } } = inputUnit.bonds;
|
||||
const bondedUnits = interBonds.getConnectedUnits(unit.id);
|
||||
const buCount = bondedUnits.length;
|
||||
|
||||
@@ -273,6 +272,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
atomicBond.bIndex = b[l] as StructureElement.UnitIndex;
|
||||
atomicBond.type = flags[l];
|
||||
atomicBond.order = order[l];
|
||||
atomicBond.key = key[l];
|
||||
if (atomicBond.test(queryCtx, true)) return true;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
atomicBond.bIndex = bond.indexB;
|
||||
atomicBond.type = bond.props.flag;
|
||||
atomicBond.order = bond.props.order;
|
||||
atomicBond.key = bond.props.key;
|
||||
if (atomicBond.test(queryCtx, true)) return true;
|
||||
}
|
||||
}
|
||||
@@ -342,4 +343,4 @@ export function isConnectedTo({ query, target, disjunct, invert, bondTest }: IsC
|
||||
|
||||
return ret.getSelection();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 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>
|
||||
@@ -322,7 +322,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
|
||||
for (const unit of structure.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = unit.bonds;
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = unit.bonds;
|
||||
atomicBond.a.unit = unit;
|
||||
atomicBond.b.unit = unit;
|
||||
for (let i = 0 as StructureElement.UnitIndex, _i = unit.elements.length; i < _i; i++) {
|
||||
@@ -335,6 +335,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
|
||||
atomicBond.b.element = unit.elements[intraBondB[lI]];
|
||||
atomicBond.type = flags[lI];
|
||||
atomicBond.order = order[lI];
|
||||
atomicBond.key = key[lI];
|
||||
// No need to "swap test" because each bond direction will be visited eventually.
|
||||
if (atomicBond.test(ctx, false)) {
|
||||
const b = structure.subsetBuilder(false);
|
||||
@@ -358,6 +359,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
|
||||
atomicBond.bIndex = bond.indexB;
|
||||
atomicBond.order = bond.props.order;
|
||||
atomicBond.type = bond.props.flag;
|
||||
atomicBond.key = bond.props.key;
|
||||
|
||||
// No need to "swap test" because each bond direction will be visited eventually.
|
||||
if (atomicBond.test(ctx, false)) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Segmentation, SortedArray } from '../../../../mol-data/int';
|
||||
@@ -370,7 +371,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
}
|
||||
|
||||
const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds;
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = inputUnitA.bonds;
|
||||
|
||||
atomicBond.setStructure(inputStructure);
|
||||
|
||||
@@ -397,6 +398,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
atomicBond.b.element = bElement;
|
||||
atomicBond.type = flags[lI];
|
||||
atomicBond.order = order[lI];
|
||||
atomicBond.key = key[lI];
|
||||
|
||||
if (atomicBond.test(ctx, true)) {
|
||||
builder.addToUnit(unit.id, bElement);
|
||||
@@ -427,6 +429,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
atomicBond.b.element = bElement;
|
||||
atomicBond.type = bond.props.flag;
|
||||
atomicBond.order = bond.props.order;
|
||||
atomicBond.key = bond.props.key;
|
||||
|
||||
if (atomicBond.test(ctx, true)) {
|
||||
builder.addToUnit(bondedUnit.unitB, bElement);
|
||||
|
||||
@@ -15,16 +15,17 @@ import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
|
||||
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, {
|
||||
readonly order: ArrayLike<number>,
|
||||
readonly flags: ArrayLike<BondType.Flag>
|
||||
readonly key: ArrayLike<number>,
|
||||
}, {
|
||||
/** can remap even with dynamicBonds on, e.g., for water molecules */
|
||||
readonly canRemap?: boolean
|
||||
}>
|
||||
|
||||
namespace IntraUnitBonds {
|
||||
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });
|
||||
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [], key: [] });
|
||||
}
|
||||
|
||||
type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag }
|
||||
type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag, readonly key: number }
|
||||
|
||||
class InterUnitBonds extends InterUnitGraph<number, StructureElement.UnitIndex, InterUnitEdgeProps> {
|
||||
/** Get inter-unit bond given a bond-location */
|
||||
|
||||
@@ -80,7 +80,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
|
||||
if (!props.forceCompute && indexPairs) {
|
||||
const { maxDistance } = indexPairs;
|
||||
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
|
||||
const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds;
|
||||
|
||||
const srcA = sourceIndex.value(aI);
|
||||
const aeI = getElementIdx(type_symbolA.value(aI));
|
||||
@@ -113,7 +113,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
}
|
||||
|
||||
if (add) {
|
||||
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
|
||||
builder.add(_aI, _bI, { order: order[i], flag: flag[i], key: key[i] });
|
||||
}
|
||||
}
|
||||
continue; // assume `indexPairs` supplies all bonds
|
||||
@@ -131,7 +131,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
// check if the bond is within MAX_RADIUS for this pair of units
|
||||
if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue;
|
||||
|
||||
builder.add(_aI, _bI, { order: se.order, flag: se.flags });
|
||||
builder.add(_aI, _bI, { order: se.order, flag: se.flags, key: se.rowIndex });
|
||||
added = true;
|
||||
}
|
||||
// assume, for an atom, that if any inter unit bond is given
|
||||
@@ -187,7 +187,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
const compIdB = label_comp_idB.value(residueIndexB[bI]);
|
||||
builder.add(_aI, _bI, {
|
||||
order: getInterBondOrderFromTable(compIdA, compIdB, atomIdA, atomIdB),
|
||||
flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed
|
||||
flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed,
|
||||
key: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,19 @@ import { Model } from '../../../model/model';
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3distance = Vec3.distance;
|
||||
|
||||
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
|
||||
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], _key: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
|
||||
const flags = new Uint16Array(builder.slotCount);
|
||||
const order = new Int8Array(builder.slotCount);
|
||||
const key = new Uint32Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(flags, _flags[i]);
|
||||
builder.assignProperty(order, _order[i]);
|
||||
builder.assignProperty(key, _key[i]);
|
||||
}
|
||||
|
||||
return builder.createGraph({ flags, order }, { canRemap });
|
||||
return builder.createGraph({ flags, order, key }, { canRemap });
|
||||
}
|
||||
|
||||
const tmpDistVecA = Vec3();
|
||||
@@ -53,7 +55,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
const { type_symbol } = unit.model.atomicHierarchy.atoms;
|
||||
const atomCount = unit.elements.length;
|
||||
const { maxDistance } = indexPairs;
|
||||
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
|
||||
const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds;
|
||||
|
||||
const { atomSourceIndex: sourceIndex } = unit.model.atomicHierarchy;
|
||||
const { invertedIndex } = Model.getInvertedAtomSourceIndex(unit.model);
|
||||
@@ -62,6 +64,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
const atomB: StructureElement.UnitIndex[] = [];
|
||||
const flags: number[] = [];
|
||||
const orders: number[] = [];
|
||||
const keys: number[] = [];
|
||||
|
||||
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
|
||||
const aI = atoms[_aI];
|
||||
@@ -104,11 +107,12 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
atomB[atomB.length] = _bI;
|
||||
orders[orders.length] = order[i];
|
||||
flags[flags.length] = flag[i];
|
||||
keys[keys.length] = key[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getGraph(atomA, atomB, orders, flags, atomCount, false);
|
||||
return getGraph(atomA, atomB, orders, flags, keys, atomCount, false);
|
||||
}
|
||||
|
||||
function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds {
|
||||
@@ -132,9 +136,10 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
const atomB: StructureElement.UnitIndex[] = [];
|
||||
const flags: number[] = [];
|
||||
const order: number[] = [];
|
||||
const key: number[] = [];
|
||||
|
||||
let lastResidue = -1;
|
||||
let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
|
||||
let componentMap: Map<string, Map<string, { flags: number, order: number, key: number }>> | undefined = void 0;
|
||||
|
||||
let isWatery = true, isDictionaryBased = true, isSequenced = true;
|
||||
|
||||
@@ -162,6 +167,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
atomB[atomB.length] = _bI;
|
||||
flags[flags.length] = se.flags;
|
||||
order[order.length] = se.order;
|
||||
key[key.length] = se.rowIndex;
|
||||
|
||||
if (!hasStructConn) structConnAdded.clear();
|
||||
hasStructConn = true;
|
||||
@@ -230,6 +236,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
flag |= BondType.Flag.MetallicCoordination;
|
||||
}
|
||||
flags[flags.length] = flag;
|
||||
key[key.length] = e.key;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -243,6 +250,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
atomB[atomB.length] = _bI;
|
||||
order[order.length] = getIntraBondOrderFromTable(compId, atomIdA, label_atom_id.value(bI));
|
||||
flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed;
|
||||
key[key.length] = -1;
|
||||
|
||||
const seqIdB = label_seq_id.value(rbI);
|
||||
|
||||
@@ -253,7 +261,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
}
|
||||
|
||||
const canRemap = isWatery || (isDictionaryBased && isSequenced);
|
||||
return getGraph(atomA, atomB, order, flags, atomCount, canRemap);
|
||||
return getGraph(atomA, atomB, order, flags, key, atomCount, canRemap);
|
||||
}
|
||||
|
||||
function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputationProps>) {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
import { Segmentation } from '../../../../mol-data/int';
|
||||
import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd';
|
||||
import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping';
|
||||
import { ElementIndex } from '../../model/indexing';
|
||||
import { ElementIndex, ResidueIndex } from '../../model/indexing';
|
||||
import { StructureElement } from '../element';
|
||||
import { Structure } from '../structure';
|
||||
import { Unit } from '../unit';
|
||||
|
||||
@@ -24,11 +25,16 @@ export interface AlignmentResult {
|
||||
failedPairs: [number, number][]
|
||||
}
|
||||
|
||||
export function alignAndSuperposeWithSIFTSMapping(structures: Structure[], options?: { traceOnly?: boolean }): AlignmentResult {
|
||||
type IncludeResidueTest = (traceElementOrFirstAtom: StructureElement.Location<Unit.Atomic>, residueIndex: ResidueIndex, startIndex: ElementIndex, endIndex: ElementIndex) => boolean
|
||||
|
||||
export function alignAndSuperposeWithSIFTSMapping(
|
||||
structures: Structure[],
|
||||
options?: { traceOnly?: boolean, includeResidueTest?: IncludeResidueTest }
|
||||
): AlignmentResult {
|
||||
const indexMap = new Map<string, IndexEntry>();
|
||||
|
||||
for (let i = 0; i < structures.length; i++) {
|
||||
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true);
|
||||
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true, options?.includeResidueTest ?? _includeAllResidues);
|
||||
}
|
||||
|
||||
const index = Array.from(indexMap.values());
|
||||
@@ -137,11 +143,16 @@ interface IndexEntry {
|
||||
pivots: { [i: number]: [unit: Unit.Atomic, start: ElementIndex, end: ElementIndex] | undefined }
|
||||
}
|
||||
|
||||
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean) {
|
||||
function _includeAllResidues() { return true; }
|
||||
|
||||
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean, includeTest: IncludeResidueTest) {
|
||||
const loc = StructureElement.Location.create<Unit.Atomic>(structure);
|
||||
|
||||
for (const unit of structure.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
const { elements, model } = unit;
|
||||
loc.unit = unit;
|
||||
|
||||
const map = SIFTSMapping.Provider.get(model).value;
|
||||
if (!map) return;
|
||||
@@ -161,9 +172,11 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
|
||||
|
||||
if (!dbName[rI]) continue;
|
||||
|
||||
const traceElement = traceElementIndex[rI];
|
||||
|
||||
let start, end;
|
||||
if (traceOnly) {
|
||||
start = traceElementIndex[rI];
|
||||
start = traceElement;
|
||||
if (start === -1) continue;
|
||||
end = start + 1 as ElementIndex;
|
||||
} else {
|
||||
@@ -171,6 +184,9 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
|
||||
end = elements[residueSegment.end - 1] + 1 as ElementIndex;
|
||||
}
|
||||
|
||||
loc.element = (traceElement >= 0 ? traceElement : start) as ElementIndex;
|
||||
if (!includeTest(loc, rI, start, end)) continue;
|
||||
|
||||
const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`;
|
||||
|
||||
if (!index.has(key)) {
|
||||
|
||||
@@ -90,6 +90,7 @@ const DownloadStructure = StateAction.build({
|
||||
url: PD.Url(''),
|
||||
format: PD.Select<BuiltInTrajectoryFormat>('mmcif', PD.arrayToOptions(BuiltInTrajectoryFormats.map(f => f[0]), f => f)),
|
||||
isBinary: PD.Boolean(false),
|
||||
label: PD.Optional(PD.Text('')),
|
||||
options
|
||||
}, { isFlat: true, label: 'URL' })
|
||||
})
|
||||
@@ -104,7 +105,7 @@ const DownloadStructure = StateAction.build({
|
||||
|
||||
switch (src.name) {
|
||||
case 'url':
|
||||
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];
|
||||
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary, label: src.params.label || undefined }];
|
||||
format = src.params.format;
|
||||
break;
|
||||
case 'pdb':
|
||||
|
||||
@@ -41,8 +41,10 @@ export namespace StructureRepresentationPresetProvider {
|
||||
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
|
||||
theme: PD.Optional(PD.Group({
|
||||
globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
|
||||
globalColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })),
|
||||
carbonColor: PD.Optional(PD.Select('chain-id', PD.arrayToOptions(['chain-id', 'operator-name', 'element-symbol'] as const))),
|
||||
symmetryColor: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
|
||||
symmetryColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })),
|
||||
focus: PD.Optional(PD.Group({
|
||||
name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
|
||||
params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any))
|
||||
@@ -76,13 +78,15 @@ export namespace StructureRepresentationPresetProvider {
|
||||
if (params.ignoreLight !== void 0) typeParams.ignoreLight = !!params.ignoreLight;
|
||||
const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
|
||||
const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined
|
||||
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) }
|
||||
: { };
|
||||
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor), ...params.theme?.globalColorParams }
|
||||
: { ...params.theme?.globalColorParams };
|
||||
const symmetryColor: ColorTheme.BuiltIn | undefined = structure && params.theme?.symmetryColor
|
||||
? isSymmetry(structure) ? params.theme?.symmetryColor : color
|
||||
: color;
|
||||
const symmetryColorParams = params.theme?.symmetryColorParams ? { ...params.theme?.globalColorParams, ...params.theme?.symmetryColorParams } : { ...params.theme?.globalColorParams };
|
||||
const globalColorParams = params.theme?.globalColorParams ? { ...params.theme?.globalColorParams } : undefined;
|
||||
|
||||
return { update, builder, color, symmetryColor, typeParams, ballAndStickColor };
|
||||
return { update, builder, color, symmetryColor, symmetryColorParams, globalColorParams, typeParams, ballAndStickColor };
|
||||
}
|
||||
|
||||
export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
|
||||
@@ -177,18 +181,18 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
const waterType = (components.water?.obj?.data?.elementCount || 0) > 50_000 ? 'line' : 'ball-and-stick';
|
||||
const lipidType = (components.lipid?.obj?.data?.elementCount || 0) > 20_000 ? 'line' : 'ball-and-stick';
|
||||
|
||||
const { update, builder, typeParams, color, symmetryColor, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, color, symmetryColor, symmetryColorParams, globalColorParams, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'ligand' }),
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
|
||||
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color, colorParams: globalColorParams }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'water' }),
|
||||
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'ion' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'lipid' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id', colorParams: globalColorParams }, { tag: 'coarse' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: false });
|
||||
@@ -223,11 +227,11 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
|
||||
smoothness: structure.isCoarseGrained ? 1.0 : 1.5,
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'protein' }),
|
||||
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'nucleic' })
|
||||
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'protein' }),
|
||||
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'nucleic' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -275,11 +279,11 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'lipid' })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'lipid' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -309,10 +313,10 @@ const polymerCartoon = StructureRepresentationPresetProvider({
|
||||
sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -367,9 +371,9 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, color, ballAndStickColor, globalColorParams } = reprBuilder(plugin, params, structure);
|
||||
const colorParams = lowResidueElementRatio && !bondsGiven
|
||||
? { carbonColor: { name: 'element-symbol', params: {} } }
|
||||
? { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams }
|
||||
: ballAndStickColor;
|
||||
|
||||
const representations = {
|
||||
@@ -377,7 +381,7 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
};
|
||||
if (showCarbohydrateSymbol) {
|
||||
Object.assign(representations, {
|
||||
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
|
||||
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color, colorParams: globalColorParams }, { tag: 'snfg-3d' }),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export class StructureRepresentationBuilder {
|
||||
}
|
||||
|
||||
applyPreset<K extends keyof PresetStructureRepresentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureRepresentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureRepresentations[K]>> | undefined
|
||||
applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
applyPreset<P = any, S extends {} = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
applyPreset(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, providerId: string, params?: any): Promise<any> | undefined
|
||||
applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationPresetProvider, params?: any): Promise<any> | undefined {
|
||||
const provider = this.resolveProvider(providerRef);
|
||||
|
||||
@@ -42,7 +42,7 @@ export class PluginComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class StatefulPluginComponent<State> extends PluginComponent {
|
||||
export class StatefulPluginComponent<State extends {}> extends PluginComponent {
|
||||
private _state: State;
|
||||
|
||||
protected updateState(...states: Partial<State>[]): boolean {
|
||||
|
||||
@@ -75,7 +75,7 @@ export const CifCoreProvider: TrajectoryFormatProvider = {
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
function directTrajectory<P>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] {
|
||||
function directTrajectory<P extends {}>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] {
|
||||
return async (plugin, data, params) => {
|
||||
const state = plugin.state.data;
|
||||
const trajectory = await state.build().to(data)
|
||||
|
||||
@@ -1079,4 +1079,4 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({
|
||||
return new SO.Shape.Provider(shape, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Icon, ArrowRightSvg, ArrowDropDownSvg } from './controls/icons';
|
||||
|
||||
export const PluginReactContext = React.createContext(void 0 as any as PluginUIContext);
|
||||
|
||||
export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> {
|
||||
export abstract class PluginUIComponent<P extends {} = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> {
|
||||
static contextType = PluginReactContext;
|
||||
readonly plugin: PluginUIContext;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorListEntry } from '../../mol-util/color/color';
|
||||
@@ -22,7 +23,7 @@ import { PluginUIContext } from '../context';
|
||||
import { ActionMenu } from './action-menu';
|
||||
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
|
||||
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg } from './icons';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, WarningSvg } from './icons';
|
||||
import { legendFor } from './legend';
|
||||
import { LineGraphComponent } from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
@@ -1466,31 +1467,38 @@ export class ConvertedControl extends React.PureComponent<ParamProps<PD.Converte
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptControl extends SimpleParam<PD.Script> {
|
||||
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
if (value !== this.props.value.expression) {
|
||||
this.update({ language: this.props.value.language, expression: value });
|
||||
export class ScriptControl extends React.PureComponent<ParamProps<PD.Script>> {
|
||||
onChange: ParamOnChange = ({ name, value }) => {
|
||||
const k = name as 'language' | 'expression';
|
||||
if (value !== this.props.value[k]) {
|
||||
this.props.onChange({ param: this.props.param, name: this.props.name, value: { ...this.props.value, [k]: value } });
|
||||
}
|
||||
};
|
||||
|
||||
onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if ((e.keyCode === 13 || e.charCode === 13 || e.key === 'Enter')) {
|
||||
if (this.props.onEnter) this.props.onEnter();
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
renderControl() {
|
||||
render() {
|
||||
// TODO: improve!
|
||||
|
||||
const placeholder = this.props.param.label || camelCaseToWords(this.props.name);
|
||||
return <input type='text'
|
||||
value={this.props.value.expression || ''}
|
||||
placeholder={placeholder}
|
||||
onChange={this.onChange}
|
||||
onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}
|
||||
disabled={this.props.isDisabled}
|
||||
/>;
|
||||
const selectParam: PD.Select<PD.Script['defaultValue']['language']> = {
|
||||
defaultValue: this.props.value.language,
|
||||
options: PD.objectToOptions(Script.Info),
|
||||
type: 'select',
|
||||
};
|
||||
const select = <SelectControl param={selectParam}
|
||||
isDisabled={this.props.isDisabled} onChange={this.onChange} onEnter={this.props.onEnter}
|
||||
name='language' value={this.props.value.language} />;
|
||||
|
||||
const textParam: PD.Text = {
|
||||
defaultValue: this.props.value.language,
|
||||
type: 'text',
|
||||
};
|
||||
const text = <TextControl param={textParam} isDisabled={this.props.isDisabled} onChange={this.onChange} name='expression' value={this.props.value.expression} />;
|
||||
|
||||
return <>
|
||||
{select}
|
||||
{this.props.value.language !== 'mol-script' && <div className='msp-help-text' style={{ padding: '10px' }}>
|
||||
<Icon svg={WarningSvg} /> Support for PyMOL, VMD, and Jmol selections is an experimental feature and may not always work as intended.
|
||||
</div>}
|
||||
{text}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
@@ -25,7 +26,7 @@ export interface ScreenshotPreviewProps {
|
||||
const _ScreenshotPreview = (props: ScreenshotPreviewProps) => {
|
||||
const { plugin, cropFrameColor } = props;
|
||||
|
||||
const helper = plugin.helpers.viewportScreenshot!;
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
const [currentCanvas, setCurrentCanvas] = useState<HTMLCanvasElement | null>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const propsRef = useRef(props);
|
||||
@@ -70,8 +71,8 @@ const _ScreenshotPreview = (props: ScreenshotPreviewProps) => {
|
||||
subscribe(plugin.state.data.behaviors.isUpdating, v => {
|
||||
if (!v) isDirty = true;
|
||||
});
|
||||
subscribe(helper.behaviors.values, () => isDirty = true);
|
||||
subscribe(helper.behaviors.cropParams, () => isDirty = true);
|
||||
subscribe(helper?.behaviors.values, () => isDirty = true);
|
||||
subscribe(helper?.behaviors.cropParams, () => isDirty = true);
|
||||
|
||||
let resizeObserver: any = void 0;
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
@@ -108,7 +109,9 @@ export const ScreenshotPreview = React.memo(_ScreenshotPreview, (prev, next) =>
|
||||
|
||||
declare const ResizeObserver: any;
|
||||
|
||||
function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) {
|
||||
function drawPreview(helper: ViewportScreenshotHelper | undefined, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) {
|
||||
if (!helper) return;
|
||||
|
||||
const { canvas, width, height } = helper.getPreview()!;
|
||||
const ctx = target.getContext('2d');
|
||||
if (!ctx) return;
|
||||
@@ -151,9 +154,9 @@ function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement
|
||||
|
||||
function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: { plugin: PluginContext, canvas: HTMLCanvasElement | null, color?: string }) {
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
const params = useBehavior(helper?.behaviors.values!);
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams!);
|
||||
const crop = useBehavior(helper?.behaviors.relativeCrop!);
|
||||
const params = useBehavior(helper?.behaviors.values);
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams);
|
||||
const crop = useBehavior(helper?.behaviors.relativeCrop);
|
||||
const cropFrameRef = useRef<Viewport>({ x: 0, y: 0, width: 0, height: 0 });
|
||||
useBehavior(params?.resolution.name === 'viewport' ? plugin.canvas3d?.resized : void 0);
|
||||
|
||||
@@ -161,7 +164,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: {
|
||||
const [start, setStart] = useState([0, 0]);
|
||||
const [current, setCurrent] = useState([0, 0]);
|
||||
|
||||
if (!helper || !canvas) return null;
|
||||
if (!helper || !canvas || !crop) return null;
|
||||
|
||||
const { width, height } = helper.getSizeAndViewport();
|
||||
|
||||
@@ -267,7 +270,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: {
|
||||
|
||||
function finish() {
|
||||
const cropFrame = cropFrameRef.current;
|
||||
if (cropParams.auto) {
|
||||
if (cropParams?.auto) {
|
||||
helper?.behaviors.cropParams.next({ ...cropParams, auto: false });
|
||||
}
|
||||
helper?.behaviors.relativeCrop.next({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../base';
|
||||
@@ -199,6 +200,9 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
const viewParams = { ...oldView };
|
||||
if (value.name === 'selection-box') {
|
||||
viewParams.radius = value.params.radius;
|
||||
} else if (value.name === 'camera-target') {
|
||||
viewParams.radius = value.params.radius;
|
||||
viewParams.dynamicDetailLevel = value.params.dynamicDetailLevel;
|
||||
} else if (value.name === 'box') {
|
||||
viewParams.bottomLeft = value.params.bottomLeft;
|
||||
viewParams.topRight = value.params.topRight;
|
||||
@@ -240,13 +244,23 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
const pivot = isEM ? 'em' : '2fo-fc';
|
||||
|
||||
const params = this.props.params as VolumeStreaming.Params;
|
||||
const entry = ((this.props.info.params as VolumeStreaming.ParamDefinition)
|
||||
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>);
|
||||
const entry = (this.props.info.params as VolumeStreaming.ParamDefinition)
|
||||
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>;
|
||||
const detailLevel = entry.params.detailLevel;
|
||||
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
|
||||
const dynamicDetailLevel = {
|
||||
...detailLevel,
|
||||
label: 'Dynamic Detail',
|
||||
defaultValue: (entry.params.view as any).map('camera-target').params.dynamicDetailLevel.defaultValue,
|
||||
};
|
||||
const selectionDetailLevel = {
|
||||
...detailLevel,
|
||||
label: 'Selection Detail',
|
||||
defaultValue: (entry.params.view as any).map('auto').params.selectionDetailLevel.defaultValue,
|
||||
};
|
||||
|
||||
const sampling = b.info.header.sampling[0];
|
||||
|
||||
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
|
||||
const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' });
|
||||
|
||||
const isUnbounded = !!(params.entry.params.view.params as any).isUnbounded;
|
||||
@@ -274,6 +288,13 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around focused element.' }),
|
||||
'camera-target': PD.Group({
|
||||
radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }),
|
||||
detailLevel: { ...detailLevel, isHidden: true },
|
||||
dynamicDetailLevel: dynamicDetailLevel,
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around camera target.' }),
|
||||
'cell': PD.Group({
|
||||
detailLevel,
|
||||
isRelative: isRelativeParam,
|
||||
@@ -282,12 +303,11 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
'auto': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
|
||||
detailLevel,
|
||||
selectionDetailLevel: { ...detailLevel, label: 'Selection Detail' },
|
||||
selectionDetailLevel: selectionDetailLevel,
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around focused element.' }),
|
||||
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
|
||||
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
|
||||
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Around Camera" shows the volume around the point the camera is targeting. "Whole Structure" shows the volume for the whole structure.' })
|
||||
};
|
||||
const options = {
|
||||
entry: params.entry.name,
|
||||
@@ -299,6 +319,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
bottomLeft: (params.entry.params.view.params as any).bottomLeft,
|
||||
topRight: (params.entry.params.view.params as any).topRight,
|
||||
selectionDetailLevel: (params.entry.params.view.params as any).selectionDetailLevel,
|
||||
dynamicDetailLevel: (params.entry.params.view.params as any).dynamicDetailLevel,
|
||||
isRelative,
|
||||
isUnbounded
|
||||
}
|
||||
|
||||
@@ -141,11 +141,13 @@ class FullSettings extends PluginUIComponent {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
|
||||
this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => {
|
||||
if (state.radiusMax !== undefined || state.radius !== undefined) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
if (this.plugin.canvas3d) {
|
||||
this.subscribe(this.plugin.canvas3d.camera.stateChanged, state => {
|
||||
if (state.radiusMax !== undefined || state.radius !== undefined) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -288,7 +288,12 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
for (const loci of this.lociArray) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
|
||||
}
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
|
||||
const reprLocis = this.props.cell.obj?.data.repr.getAllLoci();
|
||||
if (reprLocis) {
|
||||
for (const loci of reprLocis) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
clearHighlight = () => {
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
|
||||
return <div className='msp-no-webgl'>
|
||||
<div>
|
||||
<p><b>WebGL does not seem to be available.</b></p>
|
||||
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
|
||||
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps. Also, make sure hardware acceleration is enabled in your browser.</p>
|
||||
<p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
import * as React from 'react';
|
||||
import { Binding } from '../../mol-util/binding';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { StateTransformer, StateSelection } from '../../mol-state';
|
||||
import { StateTransformer, StateSelection, State } from '../../mol-state';
|
||||
import { SelectLoci } from '../../mol-plugin/behavior/dynamic/representation';
|
||||
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
|
||||
import { Icon, ArrowDropDownSvg, ArrowRightSvg, CameraSvg } from '../controls/icons';
|
||||
import { Button } from '../controls/common';
|
||||
import { memoizeLatest } from '../../mol-util/memoize';
|
||||
|
||||
function getBindingsList(bindings: { [k: string]: Binding }) {
|
||||
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]);
|
||||
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]).filter(b => Binding.isBinding(b[1]));
|
||||
}
|
||||
|
||||
export class BindingsHelp extends React.PureComponent<{ bindings: { [k: string]: Binding } }> {
|
||||
@@ -77,19 +78,30 @@ export class ViewportHelpContent extends PluginUIComponent<{ selectOnly?: boolea
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
render() {
|
||||
const interactionBindings: { [k: string]: Binding } = {};
|
||||
this.plugin.spec.behaviors.forEach(b => {
|
||||
const { bindings } = b.defaultParams;
|
||||
if (bindings) Object.assign(interactionBindings, bindings);
|
||||
getInteractionBindings = memoizeLatest((cells: State.Cells) => {
|
||||
let interactionBindings: { [k: string]: Binding } | undefined = void 0;
|
||||
|
||||
cells.forEach(c => {
|
||||
const params = c.params?.values;
|
||||
if (params?.bindings && Object.keys(params.bindings).length > 0) {
|
||||
if (!interactionBindings) interactionBindings = { };
|
||||
Object.assign(interactionBindings, params.bindings);
|
||||
}
|
||||
});
|
||||
|
||||
return interactionBindings;
|
||||
});
|
||||
|
||||
render() {
|
||||
const interactionBindings = this.getInteractionBindings(this.plugin.state.behaviors.cells);
|
||||
|
||||
return <>
|
||||
{(!this.props.selectOnly && this.plugin.canvas3d) && <HelpGroup key='trackball' header='Moving in 3D'>
|
||||
<BindingsHelp bindings={this.plugin.canvas3d.props.trackball.bindings} />
|
||||
</HelpGroup>}
|
||||
<HelpGroup key='interactions' header='Mouse Controls'>
|
||||
{!!interactionBindings && <HelpGroup key='interactions' header='Mouse Controls'>
|
||||
<BindingsHelp bindings={interactionBindings} />
|
||||
</HelpGroup>
|
||||
</HelpGroup>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -96,18 +96,21 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
|
||||
}
|
||||
|
||||
function ScreenshotParams({ plugin, isDisabled }: { plugin: PluginContext, isDisabled: boolean }) {
|
||||
const helper = plugin.helpers.viewportScreenshot!;
|
||||
const values = useBehavior(helper.behaviors.values);
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
|
||||
const values = useBehavior(helper?.behaviors.values);
|
||||
if (!helper) return null;
|
||||
|
||||
return <ParameterControls params={helper.params} values={values} onChangeValues={v => helper.behaviors.values.next(v)} isDisabled={isDisabled} />;
|
||||
}
|
||||
|
||||
function CropControls({ plugin }: { plugin: PluginContext }) {
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams!);
|
||||
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams);
|
||||
useBehavior(helper?.behaviors.relativeCrop);
|
||||
|
||||
if (!helper) return null;
|
||||
if (!helper || !cropParams) return null;
|
||||
|
||||
return <div style={{ width: '100%', height: '24px', marginTop: '8px' }}>
|
||||
<ToggleButton icon={CropOrginalSvg} title='Auto-crop' inline isSelected={cropParams.auto}
|
||||
|
||||
@@ -22,6 +22,8 @@ import { ViewportHelpContent } from './help';
|
||||
|
||||
export class SimpleSettingsControl extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
|
||||
this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => {
|
||||
@@ -133,6 +135,7 @@ const SimpleSettingsMapping = ParamMapping({
|
||||
canvas.cameraClipping = {
|
||||
radius: s.clipping.radius,
|
||||
far: s.clipping.far,
|
||||
minNear: s.clipping.minNear,
|
||||
};
|
||||
|
||||
props.layout = s.layout;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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 Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateTransform, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
@@ -39,7 +40,7 @@ namespace PluginBehavior {
|
||||
'misc': 'Miscellaneous'
|
||||
};
|
||||
|
||||
export interface CreateParams<P> {
|
||||
export interface CreateParams<P extends {}> {
|
||||
name: string,
|
||||
category: keyof typeof Categories,
|
||||
ctor: Ctor<P>,
|
||||
@@ -72,7 +73,7 @@ namespace PluginBehavior {
|
||||
return categoryMap.get(t.id)!;
|
||||
}
|
||||
|
||||
export function create<P>(params: CreateParams<P>) {
|
||||
export function create<P extends {}>(params: CreateParams<P>) {
|
||||
const t = PluginStateTransform.CreateBuiltIn<Category, Behavior, P>({
|
||||
name: params.name,
|
||||
display: params.display,
|
||||
@@ -112,7 +113,7 @@ namespace PluginBehavior {
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class Handler<P = { }> implements PluginBehavior<P> {
|
||||
export abstract class Handler<P extends {} = {}> implements PluginBehavior<P> {
|
||||
private subs: PluginCommand.Subscription[] = [];
|
||||
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
|
||||
this.subs.push(cmd.subscribe(this.ctx, action));
|
||||
@@ -144,8 +145,18 @@ namespace PluginBehavior {
|
||||
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
|
||||
this.subs.push(cmd.subscribe(this.plugin, action));
|
||||
}
|
||||
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void) {
|
||||
this.subs.push(o.subscribe(action));
|
||||
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void): PluginCommand.Subscription {
|
||||
const sub = o.subscribe(action);
|
||||
this.subs.push(sub);
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
const idx = this.subs.indexOf(sub);
|
||||
if (idx >= 0) {
|
||||
this.subs.splice(idx, 1);
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
dispose(): void {
|
||||
for (const s of this.subs) s.unsubscribe();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
@@ -24,6 +25,10 @@ import { PluginContext } from '../../../context';
|
||||
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
|
||||
import { distinctUntilChanged, filter, map, Observable, throttleTime } from 'rxjs';
|
||||
import { Camera } from '../../../../mol-canvas3d/camera';
|
||||
import { PluginCommand } from '../../../command';
|
||||
import { SingleAsyncQueue } from '../../../../mol-util/single-async-queue';
|
||||
|
||||
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
|
||||
|
||||
@@ -53,7 +58,7 @@ export namespace VolumeStreaming {
|
||||
valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }]
|
||||
};
|
||||
|
||||
export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = { }) {
|
||||
export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = {}) {
|
||||
const { data, defaultView, channelParams } = options;
|
||||
const map = new Map<string, VolumeServerInfo.EntryData>();
|
||||
if (data) data.entries.forEach(d => map.set(d.dataId, d));
|
||||
@@ -68,7 +73,7 @@ export namespace VolumeStreaming {
|
||||
export type EntryParams = PD.Values<EntryParamDefinition>
|
||||
|
||||
export function createEntryParams(options: { entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure, channelParams?: DefaultChannelParams }) {
|
||||
const { entryData, defaultView, structure, channelParams = { } } = options;
|
||||
const { entryData, defaultView, structure, channelParams = {} } = options;
|
||||
|
||||
// fake the info
|
||||
const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: Volume.IsoValue.relative(0) };
|
||||
@@ -86,19 +91,24 @@ export namespace VolumeStreaming {
|
||||
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
}, { description: 'Box around focused element.', isFlat: true }),
|
||||
'camera-target': PD.Group({
|
||||
radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }),
|
||||
// Minimal detail level for the inside of the zoomed region (real detail can be higher, depending on the region size)
|
||||
dynamicDetailLevel: createDetailParams(info.header.availablePrecisions, 0, { label: 'Dynamic Detail' }),
|
||||
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
}, { description: 'Box around camera target.', isFlat: true }),
|
||||
'cell': PD.Group<{}>({}),
|
||||
// Show selection-box if available and cell otherwise.
|
||||
'auto': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
|
||||
selectionDetailLevel: PD.Select<number>(Math.min(6, info.header.availablePrecisions.length - 1),
|
||||
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { label: 'Selection Detail', description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
|
||||
selectionDetailLevel: createDetailParams(info.header.availablePrecisions, 6, { label: 'Selection Detail' }),
|
||||
isSelection: PD.Boolean(false, { isHidden: true }),
|
||||
bottomLeft: PD.Vec3(box.min, {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(box.max, {}, { isHidden: true }),
|
||||
}, { description: 'Box around focused element.', isFlat: true })
|
||||
}, { options: ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the focused element/atom. "Whole Structure" shows the volume for the whole structure.' }),
|
||||
detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1),
|
||||
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
|
||||
detailLevel: createDetailParams(info.header.availablePrecisions, 3),
|
||||
channels: info.kind === 'em'
|
||||
? PD.Group({
|
||||
'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
|
||||
@@ -111,13 +121,40 @@ export namespace VolumeStreaming {
|
||||
};
|
||||
}
|
||||
|
||||
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][];
|
||||
function createDetailParams(availablePrecisions: VolumeServerHeader.DetailLevel[], preferredPrecision: number, info?: PD.Info) {
|
||||
return PD.Select<number>(Math.min(preferredPrecision, availablePrecisions.length - 1),
|
||||
availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]),
|
||||
{
|
||||
description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 1 (0.52M voxels) to 7 (25.17M voxels).',
|
||||
...info
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell' | 'auto'
|
||||
export function copyParams(origParams: Params): Params {
|
||||
return {
|
||||
entry: {
|
||||
name: origParams.entry.name,
|
||||
params: {
|
||||
detailLevel: origParams.entry.params.detailLevel,
|
||||
channels: origParams.entry.params.channels,
|
||||
view: {
|
||||
name: origParams.entry.params.view.name,
|
||||
params: { ...origParams.entry.params.view.params } as any,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['camera-target', 'Around Camera'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][];
|
||||
|
||||
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'camera-target' | 'cell' | 'auto'
|
||||
|
||||
export type ParamDefinition = ReturnType<typeof createParams>
|
||||
export type Params = PD.Values<ParamDefinition>
|
||||
|
||||
|
||||
type ChannelsInfo = { [name in ChannelType]?: { isoValue: Volume.IsoValue, color: Color, wireframe: boolean, opacity: number } }
|
||||
type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: Volume }
|
||||
|
||||
@@ -140,6 +177,14 @@ export namespace VolumeStreaming {
|
||||
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
|
||||
private ref: string = '';
|
||||
public infoMap: Map<string, VolumeServerInfo.EntryData>;
|
||||
private updateQueue: SingleAsyncQueue;
|
||||
private cameraTargetObservable = this.plugin.canvas3d!.didDraw!.pipe(
|
||||
throttleTime(500, undefined, { 'leading': true, 'trailing': true }),
|
||||
map(() => this.plugin.canvas3d?.camera.getSnapshot()),
|
||||
distinctUntilChanged((a, b) => this.isCameraTargetSame(a, b)),
|
||||
filter(a => a !== undefined),
|
||||
) as Observable<Camera.Snapshot>;
|
||||
private cameraTargetSubscription?: PluginCommand.Subscription = undefined;
|
||||
|
||||
channels: Channels = {};
|
||||
|
||||
@@ -163,6 +208,9 @@ export namespace VolumeStreaming {
|
||||
if (this.params.entry.params.view.name === 'auto' && this.params.entry.params.view.params.isSelection) {
|
||||
detail = this.params.entry.params.view.params.selectionDetailLevel;
|
||||
}
|
||||
if (this.params.entry.params.view.name === 'camera-target' && box) {
|
||||
detail = this.decideDetail(box, this.params.entry.params.view.params.dynamicDetailLevel);
|
||||
}
|
||||
|
||||
url += `?detail=${detail}`;
|
||||
|
||||
@@ -201,58 +249,21 @@ export namespace VolumeStreaming {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private updateSelectionBoxParams(box: Box3D) {
|
||||
if (this.params.entry.params.view.name !== 'selection-box') return;
|
||||
private async updateParams(box: Box3D | undefined, autoIsSelection: boolean = false) {
|
||||
const newParams = copyParams(this.params);
|
||||
const viewType = newParams.entry.params.view.name;
|
||||
if (viewType !== 'off' && viewType !== 'cell') {
|
||||
newParams.entry.params.view.params.bottomLeft = box?.min || Vec3.zero();
|
||||
newParams.entry.params.view.params.topRight = box?.max || Vec3.zero();
|
||||
}
|
||||
if (viewType === 'auto') {
|
||||
newParams.entry.params.view.params.isSelection = autoIsSelection;
|
||||
}
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
const newParams: Params = {
|
||||
...this.params,
|
||||
entry: {
|
||||
name: this.params.entry.name,
|
||||
params: {
|
||||
...this.params.entry.params,
|
||||
view: {
|
||||
name: 'selection-box' as const,
|
||||
params: {
|
||||
radius: this.params.entry.params.view.params.radius,
|
||||
bottomLeft: box.min,
|
||||
topRight: box.max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const update = state.build().to(this.ref).update(newParams);
|
||||
|
||||
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
|
||||
}
|
||||
|
||||
private updateAutoParams(box: Box3D | undefined, isSelection: boolean) {
|
||||
if (this.params.entry.params.view.name !== 'auto') return;
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
const newParams: Params = {
|
||||
...this.params,
|
||||
entry: {
|
||||
name: this.params.entry.name,
|
||||
params: {
|
||||
...this.params.entry.params,
|
||||
view: {
|
||||
name: 'auto' as const,
|
||||
params: {
|
||||
radius: this.params.entry.params.view.params.radius,
|
||||
selectionDetailLevel: this.params.entry.params.view.params.selectionDetailLevel,
|
||||
isSelection,
|
||||
bottomLeft: box?.min || Vec3.zero(),
|
||||
topRight: box?.max || Vec3.zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const update = state.build().to(this.ref).update(newParams);
|
||||
|
||||
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
|
||||
}
|
||||
|
||||
private getStructureRoot() {
|
||||
@@ -303,6 +314,18 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
}
|
||||
|
||||
private isCameraTargetSame(a?: Camera.Snapshot, b?: Camera.Snapshot): boolean {
|
||||
if (!a || !b) return false;
|
||||
const targetSame = Vec3.equals(a.target, b.target);
|
||||
const sqDistA = Vec3.squaredDistance(a.target, a.position);
|
||||
const sqDistB = Vec3.squaredDistance(b.target, b.position);
|
||||
const distanceSame = Math.abs(sqDistA - sqDistB) / sqDistA < 1e-3;
|
||||
return targetSame && distanceSame;
|
||||
}
|
||||
private cameraTargetDistance(snapshot: Camera.Snapshot): number {
|
||||
return Vec3.distance(snapshot.target, snapshot.position);
|
||||
}
|
||||
|
||||
private _invTransform: Mat4 = Mat4();
|
||||
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) {
|
||||
@@ -328,39 +351,82 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
|
||||
private updateAuto(loci: StructureElement.Loci | EmptyLoci) {
|
||||
// if (Loci.areEqual(this.lastLoci, loci)) {
|
||||
// this.lastLoci = EmptyLoci;
|
||||
// this.updateSelectionBoxParams(Box3D.empty());
|
||||
// return;
|
||||
// }
|
||||
|
||||
this.lastLoci = loci;
|
||||
|
||||
if (isEmptyLoci(loci)) {
|
||||
this.updateAutoParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const box = this.getBoxFromLoci(loci);
|
||||
this.updateAutoParams(box, true);
|
||||
this.updateQueue.enqueue(async () => {
|
||||
this.lastLoci = loci;
|
||||
if (isEmptyLoci(loci)) {
|
||||
await this.updateParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false);
|
||||
} else {
|
||||
await this.updateParams(this.getBoxFromLoci(loci), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateSelectionBox(loci: StructureElement.Loci | EmptyLoci) {
|
||||
if (Loci.areEqual(this.lastLoci, loci)) {
|
||||
this.lastLoci = EmptyLoci;
|
||||
this.updateSelectionBoxParams(Box3D());
|
||||
return;
|
||||
this.updateQueue.enqueue(async () => {
|
||||
if (Loci.areEqual(this.lastLoci, loci)) {
|
||||
this.lastLoci = EmptyLoci;
|
||||
} else {
|
||||
this.lastLoci = loci;
|
||||
}
|
||||
const box = this.getBoxFromLoci(this.lastLoci);
|
||||
await this.updateParams(box);
|
||||
});
|
||||
}
|
||||
|
||||
private updateCameraTarget(snapshot: Camera.Snapshot) {
|
||||
this.updateQueue.enqueue(async () => {
|
||||
const origManualReset = this.plugin.canvas3d?.props.camera.manualReset;
|
||||
try {
|
||||
if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: true } });
|
||||
const box = this.boxFromCameraTarget(snapshot, true);
|
||||
await this.updateParams(box);
|
||||
} finally {
|
||||
if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: origManualReset } });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boxFromCameraTarget(snapshot: Camera.Snapshot, boundByBoundarySize: boolean): Box3D {
|
||||
const target = snapshot.target;
|
||||
const distance = this.cameraTargetDistance(snapshot);
|
||||
const top = Math.tan(0.5 * snapshot.fov) * distance;
|
||||
let radius = top;
|
||||
const viewport = this.plugin.canvas3d?.camera.viewport;
|
||||
if (viewport && viewport.width > viewport.height) {
|
||||
radius *= viewport.width / viewport.height;
|
||||
}
|
||||
|
||||
this.lastLoci = loci;
|
||||
|
||||
if (isEmptyLoci(loci)) {
|
||||
this.updateSelectionBoxParams(Box3D());
|
||||
return;
|
||||
const relativeRadius = this.params.entry.params.view.name === 'camera-target' ? this.params.entry.params.view.params.radius : 0.5;
|
||||
radius *= relativeRadius;
|
||||
let radiusX, radiusY, radiusZ;
|
||||
if (boundByBoundarySize) {
|
||||
const bBoxSize = Vec3.zero();
|
||||
Box3D.size(bBoxSize, this.data.structure.boundary.box);
|
||||
radiusX = Math.min(radius, 0.5 * bBoxSize[0]);
|
||||
radiusY = Math.min(radius, 0.5 * bBoxSize[1]);
|
||||
radiusZ = Math.min(radius, 0.5 * bBoxSize[2]);
|
||||
} else {
|
||||
radiusX = radiusY = radiusZ = radius;
|
||||
}
|
||||
return Box3D.create(
|
||||
Vec3.create(target[0] - radiusX, target[1] - radiusY, target[2] - radiusZ),
|
||||
Vec3.create(target[0] + radiusX, target[1] + radiusY, target[2] + radiusZ)
|
||||
);
|
||||
}
|
||||
|
||||
const box = this.getBoxFromLoci(loci);
|
||||
this.updateSelectionBoxParams(box);
|
||||
private decideDetail(box: Box3D, baseDetail: number): number {
|
||||
const cellVolume = this.info.kind === 'x-ray'
|
||||
? Box3D.volume(this.data.structure.boundary.box)
|
||||
: this.info.header.spacegroup.size.reduce((a, b) => a * b, 1);
|
||||
const boxVolume = Box3D.volume(box);
|
||||
let ratio = boxVolume / cellVolume;
|
||||
const maxDetail = this.info.header.availablePrecisions.length - 1;
|
||||
let detail = baseDetail;
|
||||
while (ratio <= 0.5 && detail < maxDetail) {
|
||||
ratio *= 2;
|
||||
detail += 1;
|
||||
}
|
||||
// console.log(`Decided dynamic detail: ${detail}, (base detail: ${baseDetail}, box/cell volume ratio: ${boxVolume / cellVolume})`);
|
||||
return detail;
|
||||
}
|
||||
|
||||
async update(params: Params) {
|
||||
@@ -369,6 +435,11 @@ export namespace VolumeStreaming {
|
||||
this.params = params;
|
||||
let box: Box3D | undefined = void 0, emptyData = false;
|
||||
|
||||
if (params.entry.params.view.name !== 'camera-target' && this.cameraTargetSubscription) {
|
||||
this.cameraTargetSubscription.unsubscribe();
|
||||
this.cameraTargetSubscription = undefined;
|
||||
}
|
||||
|
||||
switch (params.entry.params.view.name) {
|
||||
case 'off':
|
||||
emptyData = true;
|
||||
@@ -388,6 +459,12 @@ export namespace VolumeStreaming {
|
||||
Box3D.expand(box, box, Vec3.create(r, r, r));
|
||||
break;
|
||||
}
|
||||
case 'camera-target':
|
||||
if (!this.cameraTargetSubscription) {
|
||||
this.cameraTargetSubscription = this.subscribeObservable(this.cameraTargetObservable, (e) => this.updateCameraTarget(e));
|
||||
}
|
||||
box = this.boxFromCameraTarget(this.plugin.canvas3d!.camera.getSnapshot(), true);
|
||||
break;
|
||||
case 'cell':
|
||||
box = this.info.kind === 'x-ray'
|
||||
? this.data.structure.boundary.box
|
||||
@@ -439,6 +516,7 @@ export namespace VolumeStreaming {
|
||||
|
||||
getDescription() {
|
||||
if (this.params.entry.params.view.name === 'selection-box') return 'Selection';
|
||||
if (this.params.entry.params.view.name === 'camera-target') return 'Camera';
|
||||
if (this.params.entry.params.view.name === 'box') return 'Static Box';
|
||||
if (this.params.entry.params.view.name === 'cell') return 'Cell';
|
||||
return '';
|
||||
@@ -449,6 +527,7 @@ export namespace VolumeStreaming {
|
||||
|
||||
this.infoMap = new Map<string, VolumeServerInfo.EntryData>();
|
||||
this.data.entries.forEach(info => this.infoMap.set(info.dataId, info));
|
||||
this.updateQueue = new SingleAsyncQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
@@ -219,6 +220,7 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
|
||||
canAutoUpdate: ({ oldParams, newParams }) => {
|
||||
return oldParams.entry.params.view === newParams.entry.params.view
|
||||
|| newParams.entry.params.view.name === 'selection-box'
|
||||
|| newParams.entry.params.view.name === 'camera-target'
|
||||
|| newParams.entry.params.view.name === 'off';
|
||||
},
|
||||
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -119,7 +119,9 @@ export function Highlight(ctx: PluginContext) {
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci: Structure.Loci(cell.obj.data) }, false);
|
||||
} else if (cell && SO.isRepresentation3D(cell.obj)) {
|
||||
const { repr } = cell.obj.data;
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci: repr.getLoci(), repr }, false);
|
||||
for (const loci of repr.getAllLoci()) {
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci, repr }, false);
|
||||
}
|
||||
} else if (SO.Molecule.Structure.Selections.is(cell.obj)) {
|
||||
for (const entry of cell.obj.data) {
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci: entry.loci }, false);
|
||||
|
||||
@@ -31,10 +31,12 @@ 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', PluginFeatureDetection.wboit),
|
||||
EnableWboit: item('plugin-config.enable-wboit', true),
|
||||
EnableDpoit: item('plugin-config.enable-dpoit', false),
|
||||
// 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', PluginFeatureDetection.preferWebGl1),
|
||||
AllowMajorPerformanceCaveat: item('plugin-config.allow-major-performance-caveat', false),
|
||||
},
|
||||
State: {
|
||||
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),
|
||||
@@ -92,4 +94,4 @@ export class PluginConfigManager {
|
||||
if (!initial) return;
|
||||
initial.forEach(([k, v]) => this._config.set(k, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,8 +200,10 @@ export class PluginContext {
|
||||
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
|
||||
const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
|
||||
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
|
||||
const enableDpoit = this.config.get(PluginConfig.General.EnableDpoit) || false;
|
||||
const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
|
||||
const failIfMajorPerformanceCaveat = !(this.config.get(PluginConfig.General.AllowMajorPerformanceCaveat) ?? false);
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, enableDpoit, preferWebGl1, failIfMajorPerformanceCaveat });
|
||||
}
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
|
||||
this.canvas3dInit.next(true);
|
||||
|
||||
@@ -29,10 +29,4 @@ export const PluginFeatureDetection = {
|
||||
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);
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -154,8 +154,8 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
|
||||
createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
|
||||
setState: (state: Partial<S>) => void
|
||||
setTheme: (theme: Theme) => void
|
||||
/** If no pickingId is given, returns a Loci for the whole representation */
|
||||
getLoci: (pickingId?: PickingId) => ModelLoci
|
||||
getLoci: (pickingId: PickingId) => ModelLoci
|
||||
getAllLoci: () => ModelLoci[]
|
||||
mark: (loci: ModelLoci, action: MarkerAction) => boolean
|
||||
destroy: () => void
|
||||
}
|
||||
@@ -227,6 +227,7 @@ namespace Representation {
|
||||
setState: () => {},
|
||||
setTheme: () => {},
|
||||
getLoci: () => EmptyLoci,
|
||||
getAllLoci: () => [],
|
||||
mark: () => false,
|
||||
destroy: () => {}
|
||||
};
|
||||
@@ -327,7 +328,7 @@ namespace Representation {
|
||||
},
|
||||
get state() { return currentState; },
|
||||
get theme() { return currentTheme; },
|
||||
getLoci: (pickingId?: PickingId) => {
|
||||
getLoci: (pickingId: PickingId) => {
|
||||
const { visuals } = currentProps;
|
||||
for (let i = 0, il = reprList.length; i < il; ++i) {
|
||||
if (!visuals || visuals.includes(reprMap[i])) {
|
||||
@@ -337,6 +338,16 @@ namespace Representation {
|
||||
}
|
||||
return EmptyLoci;
|
||||
},
|
||||
getAllLoci: () => {
|
||||
const loci: ModelLoci[] = [];
|
||||
const { visuals } = currentProps;
|
||||
for (let i = 0, il = reprList.length; i < il; ++i) {
|
||||
if (!visuals || visuals.includes(reprMap[i])) {
|
||||
loci.push(...reprList[i].getAllLoci());
|
||||
}
|
||||
}
|
||||
return loci;
|
||||
},
|
||||
mark: (loci: ModelLoci, action: MarkerAction) => {
|
||||
let marked = false;
|
||||
for (let i = 0, il = reprList.length; i < il; ++i) {
|
||||
@@ -399,6 +410,10 @@ namespace Representation {
|
||||
// TODO
|
||||
return EmptyLoci;
|
||||
},
|
||||
getAllLoci: () => {
|
||||
// TODO
|
||||
return [];
|
||||
},
|
||||
mark: (loci: ModelLoci, action: MarkerAction) => {
|
||||
// TODO
|
||||
return false;
|
||||
|
||||
@@ -213,14 +213,16 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
|
||||
get geometryVersion() { return geometryVersion; },
|
||||
updated,
|
||||
createOrUpdate,
|
||||
getLoci(pickingId?: PickingId) {
|
||||
if (pickingId === undefined) return Shape.Loci(_shape);
|
||||
getLoci(pickingId: PickingId) {
|
||||
const { objectId, groupId, instanceId } = pickingId;
|
||||
if (_renderObject && _renderObject.id === objectId) {
|
||||
return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }]);
|
||||
}
|
||||
return EmptyLoci;
|
||||
},
|
||||
getAllLoci() {
|
||||
return [Shape.Loci(_shape)];
|
||||
},
|
||||
mark(loci: Loci, action: MarkerAction) {
|
||||
if (!MarkerActions.is(_state.markerActions, action)) return false;
|
||||
if (ShapeGroup.isLoci(loci) || Shape.isLoci(loci)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -72,11 +72,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
});
|
||||
}
|
||||
|
||||
function getLoci(pickingId?: PickingId) {
|
||||
if (pickingId === undefined) return Structure.Loci(_structure.target);
|
||||
function getLoci(pickingId: PickingId) {
|
||||
return visual ? visual.getLoci(pickingId) : EmptyLoci;
|
||||
}
|
||||
|
||||
function getAllLoci() {
|
||||
return [Structure.Loci(_structure.target)];
|
||||
}
|
||||
|
||||
function mark(loci: Loci, action: MarkerAction) {
|
||||
if (!_structure) return false;
|
||||
if (!MarkerActions.is(_state.markerActions, action)) return false;
|
||||
@@ -157,6 +160,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
setState,
|
||||
setTheme,
|
||||
getLoci,
|
||||
getAllLoci,
|
||||
mark,
|
||||
destroy
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -185,8 +185,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
});
|
||||
}
|
||||
|
||||
function getLoci(pickingId?: PickingId) {
|
||||
if (pickingId === undefined) return Structure.Loci(_structure.target);
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci;
|
||||
visuals.forEach(({ visual }) => {
|
||||
const _loci = visual.getLoci(pickingId);
|
||||
@@ -195,6 +194,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
return loci;
|
||||
}
|
||||
|
||||
function getAllLoci() {
|
||||
return [Structure.Loci(_structure.target)];
|
||||
}
|
||||
|
||||
function mark(loci: Loci, action: MarkerAction) {
|
||||
if (!_structure) return false;
|
||||
if (!MarkerActions.is(_state.markerActions, action)) return false;
|
||||
@@ -302,6 +305,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
setState,
|
||||
setTheme,
|
||||
getLoci,
|
||||
getAllLoci,
|
||||
mark,
|
||||
destroy
|
||||
};
|
||||
|
||||
@@ -358,10 +358,12 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
|
||||
createOrUpdate,
|
||||
setState,
|
||||
setTheme,
|
||||
getLoci: (pickingId?: PickingId): Loci => {
|
||||
if (pickingId === undefined) return getLoci(_volume, _props);
|
||||
getLoci: (pickingId: PickingId): Loci => {
|
||||
return visual ? visual.getLoci(pickingId) : EmptyLoci;
|
||||
},
|
||||
getAllLoci: (): Loci[] => {
|
||||
return [getLoci(_volume, _props)];
|
||||
},
|
||||
mark,
|
||||
destroy
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 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>
|
||||
@@ -333,6 +333,7 @@ const bondProperty = {
|
||||
|
||||
flags: bondProp(Types.BondFlags),
|
||||
order: bondProp(Type.Num),
|
||||
key: bondProp(Type.Num),
|
||||
length: bondProp(Type.Num),
|
||||
atomA: bondProp(Types.ElementReference),
|
||||
atomB: bondProp(Types.ElementReference)
|
||||
@@ -356,5 +357,5 @@ export const structureQuery = {
|
||||
combinator,
|
||||
atomSet,
|
||||
atomProperty,
|
||||
bondProperty: bondProperty
|
||||
bondProperty
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 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>
|
||||
@@ -211,21 +211,21 @@ const symbols = [
|
||||
// ============= FILTERS ================
|
||||
D(MolScript.structureQuery.filter.pick, (ctx, xs) => Queries.filters.pick(xs[0] as any, xs['test'])(ctx)),
|
||||
D(MolScript.structureQuery.filter.first, (ctx, xs) => Queries.filters.first(xs[0] as any)(ctx)),
|
||||
D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'] as any)(ctx)),
|
||||
D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'])(ctx)),
|
||||
D(MolScript.structureQuery.filter.intersectedBy, (ctx, xs) => Queries.filters.areIntersectedBy(xs[0] as any, xs['by'] as any)(ctx)),
|
||||
D(MolScript.structureQuery.filter.within, (ctx, xs) => Queries.filters.within({
|
||||
query: xs[0] as any,
|
||||
target: xs['target'] as any,
|
||||
minRadius: xs['min-radius'] as any,
|
||||
maxRadius: xs['max-radius'] as any,
|
||||
minRadius: xs['min-radius']?.(ctx) as any,
|
||||
maxRadius: xs['max-radius']?.(ctx) as any,
|
||||
elementRadius: xs['atom-radius'] as any,
|
||||
invert: xs['invert'] as any
|
||||
invert: xs['invert']?.(ctx) as any
|
||||
})(ctx)),
|
||||
D(MolScript.structureQuery.filter.isConnectedTo, (ctx, xs) => Queries.filters.isConnectedTo({
|
||||
query: xs[0] as any,
|
||||
target: xs['target'] as any,
|
||||
disjunct: xs['disjunct'] as any,
|
||||
invert: xs['invert'] as any,
|
||||
disjunct: xs['disjunct']?.(ctx) as any,
|
||||
invert: xs['invert']?.(ctx) as any,
|
||||
bondTest: xs['bond-test']
|
||||
})(ctx)),
|
||||
|
||||
@@ -248,6 +248,9 @@ const symbols = [
|
||||
D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) {
|
||||
return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx);
|
||||
}),
|
||||
D(MolScript.structureQuery.generator.queryInSelection, function structureQuery_generator_queryInSelection(ctx, xs) {
|
||||
return Queries.generators.querySelection(xs[0] as any, xs['query'] as any, xs['in-complement']?.(ctx) as any)(ctx);
|
||||
}),
|
||||
|
||||
// ============= MODIFIERS ================
|
||||
|
||||
@@ -278,6 +281,7 @@ const symbols = [
|
||||
fixedPoint: xs['fixed-point']?.(ctx) ?? false
|
||||
})(ctx);
|
||||
}),
|
||||
D(MolScript.structureQuery.modifier.intersectBy, function structureQuery_modifier_intersectBy(ctx, xs) { return Queries.modifiers.intersectBy(xs[0] as any, xs['by'] as any)(ctx); }),
|
||||
|
||||
// ============= COMBINATORS ================
|
||||
|
||||
@@ -353,9 +357,27 @@ const symbols = [
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureFlags, atomProp(StructureProperties.residue.secondary_structure_type)),
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.chemCompType, atomProp(StructureProperties.residue.chem_comp_type)),
|
||||
|
||||
// ============= ATOM SET ================
|
||||
|
||||
D(MolScript.structureQuery.atomSet.atomCount,
|
||||
function structureQuery_atomset_atomCount(ctx, xs) {
|
||||
return Queries.atomset.atomCount(ctx);
|
||||
}),
|
||||
|
||||
D(MolScript.structureQuery.atomSet.countQuery,
|
||||
function structureQuery_atomset_countQuery(ctx, xs) {
|
||||
return Queries.atomset.countQuery(xs[0] as any)(ctx);
|
||||
}),
|
||||
|
||||
D(MolScript.structureQuery.atomSet.propertySet,
|
||||
function structureQuery_atomset_propertySet(ctx, xs) {
|
||||
return Queries.atomset.propertySet(xs[0] as any)(ctx);
|
||||
}),
|
||||
|
||||
// ============= BOND PROPERTIES ================
|
||||
D(MolScript.structureQuery.bondProperty.order, (ctx, xs) => ctx.atomicBond.order),
|
||||
D(MolScript.structureQuery.bondProperty.flags, (ctx, xs) => ctx.atomicBond.type),
|
||||
D(MolScript.structureQuery.bondProperty.key, (ctx, xs) => ctx.atomicBond.key),
|
||||
D(MolScript.structureQuery.bondProperty.atomA, (ctx, xs) => ctx.atomicBond.a),
|
||||
D(MolScript.structureQuery.bondProperty.atomB, (ctx, xs) => ctx.atomicBond.b),
|
||||
D(MolScript.structureQuery.bondProperty.length, (ctx, xs) => ctx.atomicBond.length),
|
||||
@@ -406,4 +428,4 @@ function getArray<T = any>(ctx: QueryContext, xs: any): T[] {
|
||||
for (const s of symbols) {
|
||||
DefaultQueryRuntimeTable.addSymbol(s);
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
|
||||
import { transpileMolScript } from './script/mol-script/symbols';
|
||||
import { parseMolScript } from './language/parser';
|
||||
import { parse } from './transpile';
|
||||
import { Expression } from './language/expression';
|
||||
import { StructureElement, QueryContext, StructureSelection, Structure, QueryFn, QueryContextOptions } from '../mol-model/structure';
|
||||
import { compile } from './runtime/query/compiler';
|
||||
import { MolScriptBuilder } from './language/builder';
|
||||
import { assertUnreachable } from '../mol-util/type-helpers';
|
||||
|
||||
export { Script };
|
||||
|
||||
@@ -20,7 +22,13 @@ function Script(expression: string, language: Script.Language): Script {
|
||||
}
|
||||
|
||||
namespace Script {
|
||||
export type Language = 'mol-script'
|
||||
export const Info = {
|
||||
'mol-script': 'Mol-Script',
|
||||
'pymol': 'PyMOL',
|
||||
'vmd': 'VMD',
|
||||
'jmol': 'Jmol',
|
||||
};
|
||||
export type Language = keyof typeof Info;
|
||||
|
||||
export function is(x: any): x is Script {
|
||||
return !!x && typeof (x as Script).expression === 'string' && !!(x as Script).language;
|
||||
@@ -36,8 +44,13 @@ namespace Script {
|
||||
const parsed = parseMolScript(script.expression);
|
||||
if (parsed.length === 0) throw new Error('No query');
|
||||
return transpileMolScript(parsed[0]);
|
||||
case 'pymol':
|
||||
case 'jmol':
|
||||
case 'vmd':
|
||||
return parse(script.language, script.expression);
|
||||
default:
|
||||
assertUnreachable(script.language);
|
||||
}
|
||||
throw new Error('unsupported script language');
|
||||
}
|
||||
|
||||
export function toQuery(script: Script): QueryFn<StructureSelection> {
|
||||
@@ -56,4 +69,4 @@ namespace Script {
|
||||
const query = compile<StructureSelection>(e);
|
||||
return query(new QueryContext(structure, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user