Compare commits

..

480 Commits

Author SHA1 Message Date
dsehnal
c2064aa318 3.28.0 2022-12-20 00:37:33 +01:00
dsehnal
109709ce8b Merge branch 'master' of https://github.com/molstar/molstar 2022-12-20 00:34:53 +01:00
midlik
b200725762 Volseg more tweaks (#674)
* Volseg: Fixed not loading volume section from state

* Volseg: Double-sided meshes (still ugly)

* Mesh extension: Detection (and correction) of inverted meshes
2022-12-20 00:34:40 +01:00
dsehnal
9cf34bf987 changelog 2022-12-20 00:27:00 +01:00
Alexander Rose
be3a91bb14 package updates 2022-12-19 15:21:10 -08:00
Alexander Rose
fc948d8b27 fix gpu mc use of float16 2022-12-19 14:41:12 -08:00
midlik
8774658f4e Volseg: Fixed not loading volume section from state (#672)
* Volseg: Fixed not loading volume section from state

* Volseg: Double-sided meshes (still ugly)
2022-12-19 23:19:53 +01:00
David Sehnal
0bf32148af Viewer setting for volumes and segmentations (#673) 2022-12-19 23:10:40 +01:00
David Sehnal
97d158b615 Volseg extenison tweaks (#671)
* improve currentVolume behavior

* use different iso-value if 1sigma isnt present in the data
2022-12-19 22:16:09 +01:00
Alexander Rose
b772dea188 tweak ignoreHydrogens non-polar handling (#669) 2022-12-19 21:59:55 +01:00
Alexander Rose
5c8f0b35ec changelog 2022-12-19 10:33:36 -08:00
dsehnal
1b382653f2 add volume controls to Volseg extension 2022-12-19 16:49:30 +01:00
midlik
c7cee63c97 Volumes & Segmentations extension (#668)
* Created CellStar state action

* CellStar: download metadata

* Right panel - CellStar UI

* CellStar: Lattice segmenatation and fitted PDB models

* CellStar: Support for source-database entry ID from metadata

* CellStar: Mesh segmentation

* CellStar: Switching between multiple entries

* CellStar: Changed default API URL

* UI updates

* CellStar: Clicking on meshes shows annotations...

* support info in Converted param

* support color/size in VolumeRepresentation3DHelpers.getDefaultParams

* support multi-visual volume representations

- repr can provide list of keys
- create visual for each key

* add volume segmentation support

- segmentation property
- segment loci & location

* add volume-segment color theme

* add volume segment representation

* use optional chaining

* add support for volume segmentation cif

* improve isosurface bounding-sphere

* fix segcif schema type

* CellStar: Highlighting segments on hover in the right panel

* CellStar: Using new Mol*-native segment visualization

* lint

* Segmentation volume can have custom segment labels

* CellStar: Segment labels for Mol*-native segments

* CellStar: Removed old implementation of segments

* CellStar: Rename CellStarLatticeSegmentationData2 -> CellStarLatticeSegmentationData

* CellStar: Default volume server is https://cellstar.ncbr.muni.cz

* CellStar: debugging

* CellStar: Fixed bug in LatticeSegmentation (scaling)

* CellStar: Partially savable state

* CellStar: WaitingSlider

* CellStar: Opacity changed via params

* Savable state for opacity

* CellStar: Changing UI in animations

* CellStar: Savable state for whole current UI

* CellStar: Savable state for segment labels

* CellStar: Source can be 'idr', CellStarVolumeServerConfig.DefaultServer

* CellStar: Select segment for lattice segmentation

* CellStar: Select segment, complete

* CellStar: Changes visible labels to "Volume & Segmentation"

* CellStar: Drop list with available entries

* CellStar: Volume type switching (partial)

* CellStar: Trying to set direct-volume control points

* CellStar: Volume visual switching

* Mesh extension: removed molstar-lib-imports

* CellStar: Global options

* CellStar: Updated file headers and `CHANGELOG.md`

* CellStar: UI controls disabled while executing change

* CellStar: Hidden state nodes, fixed bug with removed global state

* CellStar: Volume opacity slider

* CellStar extension renamed to Volseg

* UI tweaks

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>
2022-12-19 16:15:23 +01:00
Alexander Rose
ba9474fa62 add stencil related webgl state and format 2022-12-16 21:47:05 -08:00
Alexander Rose
b7ec7ea686 Merge pull request #665 from molstar/solid-impostor
Solid impostors
2022-12-16 21:41:14 -08:00
Alexander Rose
74560adb08 changelog 2022-12-16 21:20:52 -08:00
Alexander Rose
e6a303bdda Merge branch 'master' of https://github.com/molstar/molstar into solid-impostor 2022-12-16 21:19:19 -08:00
David Sehnal
7afcf0bb68 Show histogram in direct volume control point settings (#666) 2022-12-16 20:02:46 +01:00
Alexander Rose
7aae2d0616 fix impostor sphere near clipping 2022-12-15 22:06:06 -08:00
dsehnal
9255505a3b 3.27.0 2022-12-15 13:02:37 +01:00
dsehnal
6c0dfd338d changelog 2022-12-15 12:59:45 +01:00
David Sehnal
c306e988c8 Ability to show only polar hydrogens (#663) 2022-12-15 12:26:34 +01:00
Alexander Rose
86f7a8b273 add impostor solidInterior param 2022-12-14 22:50:15 -08:00
David Sehnal
2428a8efab Merge pull request #660 from molstar/struct-conn-ions-fix
Fix struct_conn bond assignment for ions
2022-12-14 10:20:22 +01:00
Alexander Rose
cba12e487f wip, solid impostor flag 2022-12-13 23:59:08 -08:00
dsehnal
e98d7931ca Fix import 2022-12-13 18:01:59 +01:00
dsehnal
f8e2d2e3d0 tweak hasStructConnRecord 2022-12-13 17:57:31 +01:00
dsehnal
4455bab6ac Fix struct_conn bond assignment for ions 2022-12-13 16:06:19 +01:00
David Sehnal
b21fb27041 Merge pull request #658 from molstar/action-params
ApplyActionControl param handling
2022-12-13 15:20:31 +01:00
dsehnal
5402ee0019 ApplyActionControl param handling 2022-12-13 15:16:19 +01:00
David Sehnal
02654ea57b Merge pull request #657 from molstar/fix-qa-assignment
Fix QualityAssessment assignment
2022-12-13 09:33:13 +01:00
dsehnal
9cd4aeabaa Merge branch 'master' of https://github.com/molstar/molstar into fix-qa-assignment 2022-12-13 09:32:42 +01:00
dsehnal
d788511a83 header 2022-12-13 09:32:20 +01:00
Alexander Rose
378b5b8096 Merge pull request #643 from arussell123/master
Add include transparent parameter for outlines
2022-12-12 21:58:49 -08:00
Alexander Rose
e26eb2f751 Merge branch 'master' into master 2022-12-12 21:58:25 -08:00
Alexander Rose
26264b15f7 tweaks 2022-12-12 21:56:34 -08:00
Alexander Rose
309e3dd981 impostor per-pixel object clipping 2022-12-12 21:51:39 -08:00
Alexander Rose
4ff5ed3b5d fix cylinder imposter cap normals 2022-12-12 21:18:37 -08:00
dsehnal
270a757756 changelog 2022-12-12 18:39:41 +01:00
dsehnal
79e4030ab4 fix qa assignment 2022-12-12 18:35:52 +01:00
David Sehnal
fd86927e04 Merge pull request #655 from MKampfrath/access-modifiers
change access modifiers
2022-12-12 16:36:38 +01:00
MKampfrath
df3a7e5037 change access modifiers 2022-12-12 16:30:40 +01:00
Alice Russell
fefc6f14c9 Change is outline enabled to is transparant outline enabled 2022-12-12 10:43:14 +00:00
Alexander Rose
923fbef0e4 remove superfluous check 2022-12-11 20:45:26 -08:00
Alexander Rose
601bd5ba7a improve impostor shaders
- fix sphere near-clipping with orthographic projection
- fix cylinder near-clipping
- add interior cylinder caps
2022-12-10 21:47:16 -08:00
Alexander Rose
5798f20c41 lint fix 2022-12-10 21:41:25 -08:00
David Sehnal
f2e26f91a8 Merge pull request #647 from dwilliams-nobias/animation-once-stops
Animation "once" fails to stop for many frames under certain conditions
2022-12-08 16:53:27 +01:00
David Sehnal
e2cdc45be6 Merge branch 'master' into animation-once-stops 2022-12-08 16:53:15 +01:00
Alice Russell
96493bc75e Fix linting 2022-12-07 14:26:48 +00:00
Alice Russell
d78ad4a78e Merge remote-tracking branch 'upstream/master' 2022-12-07 12:25:22 +00:00
Alice Russell
0c54b0dd6e Add option to toogle transparent outline 2022-12-07 12:21:31 +00:00
Alexander Rose
65310e52de guard against issue with bumpiness in impostors 2022-12-06 22:48:22 -08:00
David Williams
285d3cd9b8 Requested changes for pull request 2022-12-06 17:36:48 -08:00
David Williams
10486abb64 Add example molecule with 1,000 animation frames 2022-12-06 17:32:44 -08:00
David Williams
8c1a9fc988 Correctly check for duration "once" for fixed or computed duration 2022-12-06 16:57:27 -08:00
Alexander Rose
7bdd0b470b 3.26.0 2022-12-04 12:18:09 -08:00
Alexander Rose
b7a51f12bf changelog 2022-12-04 12:12:22 -08:00
Alexander Rose
e002ac5474 Merge pull request #644 from molstar/power-pref
support power-preference webgl attribute
2022-12-04 12:09:46 -08:00
Alexander Rose
211d339ce0 Merge branch 'master' into power-pref 2022-12-04 12:09:39 -08:00
Alexander Rose
509fb14473 Merge pull request #645 from molstar/structure-mol-surf
add structure molecular surface visual
2022-12-04 12:09:14 -08:00
Alexander Rose
ef838a1b83 Merge branch 'master' into structure-mol-surf 2022-12-04 12:09:05 -08:00
Alexander Rose
f84b5b633d improve Representation typing 2022-12-04 09:45:12 -08:00
Alexander Rose
8ec8c1170d comment 2022-12-04 09:32:02 -08:00
David Sehnal
920b98e4d1 Merge pull request #604 from molstar/volume-theme
by-volume-value theme
2022-12-04 09:18:32 +01:00
dsehnal
c80f52d4bc Merge branch 'master' of https://github.com/molstar/molstar into volume-theme 2022-12-04 09:14:41 +01:00
dsehnal
0b6aa42daf tweaks 2022-12-04 09:14:06 +01:00
Alexander Rose
04dc6427ef update SaccharideNames 2022-12-03 22:00:24 -08:00
Alexander Rose
77e366b484 schema updates 2022-12-03 21:58:04 -08:00
Alexander Rose
add7cfa0f2 package updates 2022-12-03 21:53:00 -08:00
Alexander Rose
14d4fa142c changelog 2022-12-03 21:46:55 -08:00
Alexander Rose
7fe1617f25 shadow param tweaks 2022-12-03 21:46:30 -08:00
Alexander Rose
cbe4cb521c add structure molecular surface visual 2022-12-03 21:32:54 -08:00
Alexander Rose
7c394525c1 Merge pull request #637 from molstar/sss
Screen-space shadows
2022-12-03 10:59:05 -08:00
Alexander Rose
12ed051fea Merge pull request #606 from jpattle/remove-caps-from-ligand-query
Remove common protein caps from ligand query
2022-12-03 10:41:00 -08:00
Alexander Rose
014913c635 support power-preference webgl attribute 2022-12-03 10:17:57 -08:00
Alexander Rose
369e45c282 handle bounds in shadow screen fade 2022-12-03 10:02:14 -08:00
Alice Russell
2892caec0c Update changelog 2022-12-02 12:31:56 +00:00
Alice Russell
eb609e918a linting fixes 2022-12-02 12:28:51 +00:00
Alice Russell
9217e58845 Add option to set minimum alpha value for outlines to be shown 2022-12-02 12:12:22 +00:00
Alexander Rose
26b633618a tweak cellpack shadow params 2022-11-27 22:22:29 -08:00
Alexander Rose
e3054d6ee1 shadows bonds check 2022-11-27 22:07:23 -08:00
Alexander Rose
0b6294f4ec check for shadow in PostprocessingPass.isEnabled 2022-11-26 22:33:11 -08:00
Alexander Rose
7b130dc0f3 cleanup 2022-11-26 22:09:24 -08:00
Alexander Rose
d93c128c25 cleanup 2022-11-26 22:04:41 -08:00
Alexander Rose
e1dd74775e refactor sss
- only sss, remove other ao implementations
- tweak params
- make sss a separate renderable
2022-11-26 21:47:08 -08:00
Alexander Rose
e61e706607 Merge branch 'master' of https://github.com/molstar/molstar into fake_shadow 2022-11-25 21:13:35 -08:00
jpattle
a57d46deaa Merge branch 'master' into remove-caps-from-ligand-query 2022-11-21 08:58:41 +00:00
Alexander Rose
60efdc0071 3.25.1 2022-11-20 10:43:31 -08:00
Alexander Rose
4dc1155a9e changelog 2022-11-20 10:38:07 -08:00
Alexander Rose
5cabe6fb42 fix auto structure-quality for coarse models 2022-11-20 10:37:30 -08:00
Alexander Rose
42239c305f better handle single-element units in Structure.eachUnitPair 2022-11-20 10:25:26 -08:00
ludovic autin
fd3c763349 All parameters exposed to try to find best parameters for different representation. 2022-11-18 09:13:42 -08:00
ludovic autin
daa9bbf2c9 fix trailing space 2022-11-17 09:31:21 -08:00
ludovic autin
c7dad00908 fake screen space shadow, second pass ssao 2022-11-17 09:28:00 -08:00
ludovic autin
24317717e8 Merge remote-tracking branch 'upstream/master' into forkdev 2022-11-16 14:01:04 -08:00
dsehnal
dd3eac6db6 3.25.0 2022-11-16 00:15:41 +01:00
dsehnal
1606f8517f changelog 2022-11-16 00:12:57 +01:00
David Sehnal
3a98401ada Merge pull request #623 from JonStargaryen/master
Fix Handling of Gzipped Assets by Reverting #615
2022-11-16 00:11:02 +01:00
Alexander Rose
4764251241 operator key tweaks 2022-11-14 21:48:44 -08:00
Sebastian Bittrich
3de04b7b9d CHANGELOG 2022-11-14 13:17:15 -08:00
Sebastian Bittrich
04908495e9 Revert "keep asset url to detect compression"
This reverts commit 2bc45c25fe.
2022-11-14 13:13:06 -08:00
Alexander Rose
3c835c848e 3.24.0 2022-11-13 20:56:58 -08:00
Alexander Rose
0f1788f122 changelog 2022-11-13 20:51:24 -08:00
Alexander Rose
3d72e700a4 package updates 2022-11-13 20:48:33 -08:00
Alexander Rose
a5cf41e65f fix viewport color for transparent background (#617) 2022-11-13 20:47:30 -08:00
Alexander Rose
7029bc41d7 Merge pull request #621 from molstar/theme-strength
add theme strength to 3d repr state
2022-11-13 20:22:14 -08:00
Alexander Rose
b87d40c844 changelog 2022-11-13 20:21:41 -08:00
Alexander Rose
9e154376d3 Merge branch 'master' of https://github.com/molstar/molstar into theme-strength 2022-11-13 20:18:56 -08:00
Alexander Rose
4a9fdbce57 Merge pull request #619 from molstar/opkey
add key to symmetry operator
2022-11-13 20:18:12 -08:00
Alexander Rose
775e335292 remove cantor-pair use 2022-11-13 20:11:42 -08:00
Alexander Rose
e553bf4deb add theme strength to 3d repr state 2022-11-12 21:58:13 -08:00
Alexander Rose
db0e09ec6e add more webgl extensions
- draw_buffers_indexed
- parallel_shader_compile
- fbo_render_mipmap
2022-11-12 20:34:18 -08:00
Alexander Rose
ba50760f92 add key to symmetry operator
- molql operatorKey
- index-pair operator prop
2022-11-12 20:30:33 -08:00
David Sehnal
524e6d4f81 Merge pull request #615 from JonStargaryen/master
keep asset url to detect compression
2022-11-11 07:47:44 +01:00
ludovic autin
db93a669ab Merge branch 'molstar:master' into forkdev 2022-11-10 13:59:26 -08:00
Sebastian Bittrich
423f5b0502 CHANGELOG 2022-11-10 13:16:27 -08:00
Sebastian Bittrich
2bc45c25fe keep asset url to detect compression 2022-11-10 13:13:49 -08:00
Jason Pattle
d341463f67 Removed unneccessary wholeResidues query from new ligand selection query as the atomGroups residue-test already covers this functionality 2022-11-07 08:37:16 +00:00
Alexander Rose
19d1ecb36a add (slow) texture-mesh position iterator 2022-11-06 23:27:17 -08:00
Alexander Rose
ffe4047f97 fix vertex color/overpaint in direct-volume shader 2022-11-06 22:24:36 -08:00
Alexander Rose
15a3c29e7a adjust external-volume theme file name 2022-11-06 22:23:15 -08:00
Alexander Rose
6a32f85e60 Merge pull request #603 from giagitom/transparent-bg-fixes 2022-11-06 16:42:27 -08:00
Jason Pattle
42ff593004 Added missing semicolon; linting 2022-11-02 10:06:26 +00:00
Jason Pattle
5140af4e6f Added myself as a contributor to package.json 2022-11-02 09:56:07 +00:00
Jason Pattle
0f5b4c00a9 Updated changelog 2022-11-02 09:54:57 +00:00
Jason Pattle
feb69f4987 Excluded whole residues which match any common protein caps from the ligand selection query 2022-11-02 09:49:44 +00:00
Jason Pattle
84eb9a58ca Added names for common protein caps 2022-11-02 09:49:07 +00:00
dsehnal
fbd96f473a External Volume theme features 2022-11-01 11:54:27 +01:00
dsehnal
69228157dc fix typo 2022-10-31 15:42:13 +01:00
dsehnal
6808f32b8d by-volume-value theme 2022-10-31 15:41:10 +01:00
giagitom
f29c62ec33 Transparent bg fix 2022-10-30 12:06:32 +01:00
Alexander Rose
d77981230b mmcif schema update 2022-10-29 12:15:29 -07:00
dsehnal
e2b92c15f0 PluginContext.initContainer options 2022-10-27 08:41:01 +02:00
dsehnal
14614f4803 3.23.0 2022-10-19 13:11:47 +02:00
dsehnal
37d3489b07 changelog 2022-10-19 13:09:02 +02:00
David Sehnal
f81225cc0d Merge pull request #592 from molstar/volume/defaults-for-em
Change EM Volume Streaming default Auto
2022-10-19 13:08:17 +02:00
dsehnal
eb47f43940 Change EM Volume Streaming default Auto 2022-10-19 13:05:16 +02:00
dsehnal
7618a5e2c9 pr template 2022-10-19 12:41:24 +02:00
David Sehnal
ab3ff842b2 Merge pull request #590 from molstar/reusable-canvas
Built-in support for mouting/unmounting PluginContext
2022-10-19 09:55:31 +02:00
dsehnal
82f0f92c15 remove unused code 2022-10-18 15:20:48 +02:00
dsehnal
545d9434d8 Add PluginContext.initContainer/canvas3dInitialized and their usage 2022-10-18 09:16:08 +02:00
dsehnal
bbc43d5113 Add PluginContext.mount/unmount 2022-10-18 08:41:29 +02:00
dsehnal
a6709acf65 3.22.0 2022-10-17 20:44:13 +02:00
dsehnal
509a027742 changelog 2022-10-17 20:41:35 +02:00
David Sehnal
7244023233 Merge pull request #589 from molstar/picking-granuality-v2
Volume.PickingGranuality custom property
2022-10-17 20:39:47 +02:00
dsehnal
c5f987d8b2 getSliceLoci tweak 2022-10-17 20:36:39 +02:00
dsehnal
793696d4c0 Volume slice granuality 2022-10-17 20:34:11 +02:00
dsehnal
305ca05f04 tweaks 2022-10-17 20:24:48 +02:00
dsehnal
f4d7d1920a typos 2022-10-17 20:12:19 +02:00
dsehnal
458aad0161 Volume.PickingGranuality custom property 2022-10-17 20:05:56 +02:00
dsehnal
9e3132461f 3.21.0 2022-10-17 17:32:57 +02:00
dsehnal
8301291215 changelog 2022-10-17 17:29:52 +02:00
David Sehnal
daed14e228 Merge pull request #588 from midlik/picking-whole-isosurfaces
New volume isosurface param pickingGranularity: voxels|surfaces
2022-10-17 17:27:34 +02:00
David Sehnal
7db82c5ba5 Merge pull request #584 from arussell123/master
Prevent component controls collapsing when option is selected
2022-10-17 16:43:01 +02:00
Alexander Rose
91d03c22c2 3.20.0 2022-10-16 21:42:39 -07:00
Alexander Rose
bc188f0d2b changelog 2022-10-16 21:37:18 -07:00
Alexander Rose
3981225824 package updates 2022-10-16 21:34:59 -07:00
Alexander Rose
1886d9d72f add structure-index color theme 2022-10-16 21:28:06 -07:00
Alexander Rose
2a7dec8892 Merge pull request #583 from jpattle/model-index-carbon-color
Model index updates & carbon color
2022-10-16 19:43:23 -07:00
Alexander Rose
35d4a5b297 Merge branch 'master' into model-index-carbon-color 2022-10-16 19:39:22 -07:00
Alexander Rose
26345bfa50 tweak 2022-10-16 17:42:43 -07:00
Alexander Rose
8c9b8676dd handle 'not enough samples' in distinctColors 2022-10-16 16:43:50 -07:00
Alexander Rose
5593c7a75f add Model.MaxIndex and use in model-index theme 2022-10-16 16:37:09 -07:00
Alexander Rose
5b70c14ffe tweak theme descriptions 2022-10-16 16:35:51 -07:00
Alexander Rose
5e4d611044 tweak changelog 2022-10-16 16:33:52 -07:00
David Sehnal
7ab9d57156 Merge pull request #545 from giagitom/lookup3d
adding nearest and distance to point methods to lookup3d
2022-10-14 21:46:33 +02:00
Adam Midlik
9ea6f51126 New volume isosurface param pickingGranularity: voxels|surfaces 2022-10-13 00:57:39 +02:00
giagitom
649fe4f4f0 Lint fix 2022-10-12 16:57:56 +02:00
giagitom
53df86c585 Avoid using unnecessary extractMinimum from heap if k=1 on nearest search, add nearest method as unreleased. 2022-10-12 16:14:51 +02:00
Alice Russell
87c708e3c1 Remove action state from being set to undefined when action selected to stop options from minimising on selection 2022-10-12 10:58:30 +01:00
Jason Pattle
ba927b0490 returned clone of theme params for model and trajectory index themes, added contributor name 2022-10-12 08:30:52 +01:00
Jason Pattle
2a09725c98 added the new model-index color theme as an option in the illustrative color-theme 2022-10-12 08:30:10 +01:00
Jason Pattle
9fa0d17933 removed carbon color adjustment option 2022-10-12 08:29:45 +01:00
Jason Pattle
8d9f8a996a Updated change log with changes to model-index and element-symbol 2022-10-12 08:29:17 +01:00
giagitom
8814b60d0b Increased performances of lookup3d nearest search. 2022-10-11 18:12:27 +02:00
Jason Pattle
541c07c53a Added a parameter to make adjusting the carbon color by the same saturation and lightness carbon colors optional 2022-10-11 16:19:17 +01:00
Jason Pattle
6cbed80815 updated the default color palette and removed the redundant model color map 2022-10-11 16:07:44 +01:00
Jason Pattle
a3c1fdc0f4 Added the model index theme provider as an option for the carbon color when selecting the element-symbol color theme 2022-10-11 15:53:05 +01:00
Jason Pattle
ddf789b01c added a new model-index color theme based off the trajectory index theme but instead using the Model.Index structure property 2022-10-11 15:50:23 +01:00
Jason Pattle
ab86cc0bf3 Renamed the model-index color theme file to trajectory-index 2022-10-11 15:37:04 +01:00
Jason Pattle
dc8fab5820 [BREAKING CHANGE] renamed the model-index color theme and its usages to trajectory-index to better reflect the functionality of the color theme 2022-10-11 15:35:29 +01:00
giagitom
813c4f845a Merge branch 'master' into lookup3d 2022-10-09 15:10:37 +02:00
Alexander Rose
6ed42e9521 add mipmap-based blur for skybox backgrounds 2022-10-08 14:54:29 -07:00
Alexander Rose
fb01ba60ec use resources object to get textures for smaa pass 2022-10-08 14:08:07 -07:00
Alexander Rose
ea4210ded5 add willReadFrequently option to sdf text 2d context 2022-10-08 14:06:25 -07:00
Alexander Rose
75e5cf54d6 remove deprecated vscode extension from recommendations 2022-10-08 14:05:12 -07:00
Jason Pattle
7cebc85a95 fixed linting warnings 2022-10-06 13:02:15 +01:00
Jason Pattle
c00faafa6d Returned a clone of the element symbol params instead of the original const, removing a todo comment 2022-10-06 11:42:53 +01:00
Jason Pattle
c9b14f0742 Updated the element symbol color theme so that the carbon color is also adjusted by saturation and brightness props 2022-10-06 09:13:33 +01:00
Alexander Rose
9624137c0d 3.19.0 2022-10-01 17:26:48 -07:00
Alexander Rose
3eb433368f changelog 2022-10-01 17:21:18 -07:00
Alexander Rose
58691f4f5f package updates 2022-10-01 17:19:59 -07:00
Alexander Rose
5e9295abd5 changelog 2022-10-01 15:45:17 -07:00
Alexander Rose
6ed0ae55b2 fix black artifacts
- on specular highlights with transparent background
2022-10-01 15:34:54 -07:00
Alexander Rose
84448d0aa1 Merge branch 'master' of https://github.com/molstar/molstar 2022-10-01 13:51:58 -07:00
Alexander Rose
31ced24966 Merge pull request #573 from molstar/optimize-binary-packing
Optimize BinaryCIF integer packing
2022-10-01 13:51:21 -07:00
Alexander Rose
24681840af debug tweaks 2022-10-01 13:34:50 -07:00
Alexander Rose
5d28aa4f2e add cameraClipping.minNear param 2022-10-01 13:32:09 -07:00
Alexander Rose
7dabdf3085 dpoit fixes
- when post-processing is off
- when rendering direct-volumes
2022-10-01 13:26:10 -07:00
giagitom
d7cbd5570c Implement lookup & grid nearest search using fibonacci heap 2022-09-30 15:44:10 +02:00
dsehnal
80011d4aea optimize BinaryCIF integer packing 2022-09-29 17:30:26 +02:00
David Sehnal
c6fe440a01 Merge pull request #569 from russellp17/fix-empty-texture-error-on-empty-canvas
Fix "empty textures" error on empty canvas
2022-09-27 16:52:28 +02:00
Russell Parker
ba8d6dc3fa Fix "empty textures" error on empty canvas 2022-09-27 10:24:55 -04:00
giagitom
378f4f8304 Merge branch 'master' into lookup3d 2022-09-19 10:22:43 +02:00
Alexander Rose
aa414485a5 3.18.0 2022-09-17 11:55:04 -07:00
Alexander Rose
3a35a5d66a changelog 2022-09-17 11:49:08 -07:00
Alexander Rose
43b0a72b09 package updates 2022-09-17 11:49:01 -07:00
Alexander Rose
521ac2d13f stereo camera improvements
- fix param updates not applied
- better param ranges and description
- add timer.mark for left/right camera
2022-09-17 11:43:18 -07:00
Alexander Rose
30520c50c2 fix changelog 2022-09-17 11:39:40 -07:00
Alexander Rose
819f07eba3 Merge pull request #533 from giagitom/dpoit
Integration of Dual depth peeling - OIT method
2022-09-17 11:36:56 -07:00
Alexander Rose
d8d6aa7136 wboit, tweak timer.mark 2022-09-17 11:34:48 -07:00
giagitom
0bdcfea276 Merge branch 'master' into lookup3d 2022-09-16 17:45:20 +02:00
giagitom
718f76313f Adding nearest method to lookup3d at unit and structure level. 2022-09-16 17:32:07 +02:00
Alexander Rose
ed75a365d8 dpoit, cleanup 2022-09-12 22:38:49 -07:00
Alexander Rose
f5ff13ffe4 dpoit, fix transparent background 2022-09-12 22:38:26 -07:00
Alexander Rose
44c69b1716 dpoit, fix depthMask not off 2022-09-12 22:27:03 -07:00
Alexander Rose
559ca7ffb8 Merge branch 'master' of https://github.com/molstar/molstar into pr/giagitom/533 2022-09-12 22:22:55 -07:00
Alexander Rose
524f34e8c1 3.17.0 2022-09-11 14:38:51 -07:00
Alexander Rose
d749be11f0 type fixes 2022-09-11 14:34:21 -07:00
Alexander Rose
13dc9ff3cb package updates 2022-09-11 14:12:57 -07:00
Alexander Rose
24b4fce326 improve RG texture format handling 2022-09-10 17:21:20 -07:00
Alexander Rose
f506210bf8 dpoit, fix webgl1 support
- in webgl1 drawbuffers must be in the same format for some reason
2022-09-10 17:12:59 -07:00
Alexander Rose
cb0cbd06ce Merge branch 'master' of https://github.com/molstar/molstar into pr/giagitom/533 2022-09-10 16:02:43 -07:00
Alexander Rose
3356239089 fix click event triggered after move 2022-09-10 15:57:07 -07:00
Alexander Rose
9a5b2edc08 cleanup unused variable 2022-09-10 15:39:07 -07:00
Alexander Rose
2d41b4bd83 dpoit, use half float for color textures when available 2022-09-10 15:36:54 -07:00
Alexander Rose
58f7758ee1 add note to MAX_DPOIT_DEPTH constant 2022-09-10 15:30:28 -07:00
Alexander Rose
9dbb642883 more blend back handling cleanup 2022-09-10 15:26:49 -07:00
Alexander Rose
c5222e4d1d Merge branch 'master' of https://github.com/molstar/molstar into pr/giagitom/533 2022-09-10 14:54:57 -07:00
Alexander Rose
a5a695a17c Merge pull request #514 from yakomaxa/molql_integration_PR
Integration of pymol/vmd/jmol transpilers from MolQL project (WIP rasmol transpiler)
2022-09-10 14:51:53 -07:00
Alexander Rose
7d1dc86cfb Merge branch 'master' into molql_integration_PR 2022-09-10 14:50:27 -07:00
Alexander Rose
03224f914a transpiler helper cleanup 2022-09-10 14:49:03 -07:00
Alexander Rose
1cf1f07232 Merge pull request #552 from molstar/perf-caveat
add support for failIfMajorPerformanceCaveat
2022-09-10 13:43:37 -07:00
Alexander Rose
838d36a74e Merge branch 'master' into perf-caveat 2022-09-10 13:43:24 -07:00
Alexander Rose
6c9300d01b fix useBehavior handling 2022-09-10 13:42:52 -07:00
Alexander Rose
3059f7efef Merge pull request #553 from molstar/pdb-ter
fix handling of PDB TER records
2022-09-10 12:51:08 -07:00
Alexander Rose
fbce7d9afa Merge branch 'master' into pdb-ter 2022-09-10 12:50:59 -07:00
Alexander Rose
1c9f3ed9fa simplify ter record check 2022-09-10 12:50:22 -07:00
Alexander Rose
8c47d2d400 Merge pull request #554 from molstar/repr-getAllLoci
add repr.getAllLoci
2022-09-10 12:46:13 -07:00
Alexander Rose
8a18f25b5d Merge branch 'master' into repr-getAllLoci 2022-09-10 12:46:05 -07:00
Alexander Rose
e7ae0058ed Merge pull request #555 from molstar/bond-key-prop
add key property to intra- and inter-bonds
2022-09-10 12:45:39 -07:00
Alexander Rose
98bf3a3e33 tweak per-group transparency cutoff 2022-09-09 23:45:02 -07:00
Alexander Rose
379fcd4494 dpoit, render volumes with standard blending 2022-09-09 23:13:15 -07:00
Alexander Rose
8589777bac Merge branch 'master' of https://github.com/molstar/molstar into pr/giagitom/533 2022-09-09 22:27:45 -07:00
Alexander Rose
c10a21ecbd add key property to intra- and inter-bonds 2022-09-09 22:19:57 -07:00
Alexander Rose
eddc616b14 add repr.getAllLoci 2022-09-09 19:23:46 -07:00
Alexander Rose
70fc1a9579 support residue ranges in jmol atom expressions 2022-09-09 15:59:14 -07:00
Alexander Rose
f27ec4d6a4 formatting 2022-09-09 15:36:08 -07:00
Alexander Rose
1e6e956e2d improve pymol keywords
- rename polymer.protein and polymer.nucleic
- fix guide "C4'"
2022-09-09 15:35:46 -07:00
Alexander Rose
0a2a3530d1 fix handling of PDB TER records 2022-09-09 13:20:15 -07:00
Alexander Rose
9e4c820e26 add support for failIfMajorPerformanceCaveat 2022-09-09 12:31:40 -07:00
Alexander Rose
05290bfe9e update renderer spec resource counts 2022-09-08 18:40:43 -07:00
Alexander Rose
607f4ce353 dpoit: fix drawbuffer setup and blend to target 2022-09-08 18:10:31 -07:00
Alexander Rose
4b819ead1d formating 2022-09-08 18:08:53 -07:00
Alexander Rose
d07d9d3f31 add missing dpoitIterations param 2022-09-08 18:08:16 -07:00
Alexander Rose
d08776bf19 Merge branch 'master' of https://github.com/molstar/molstar into pr/yakomaxa/514 2022-09-07 18:37:37 -07:00
Alexander Rose
7a61fd44fd remove rasmol transpiler
- functionallity integrated into jmol transpiler
2022-09-07 18:32:43 -07:00
Alexander Rose
151da1487c formatting 2022-09-07 18:28:45 -07:00
Alexander Rose
e3f6dfad5b improve jmol transpiler
- add basic within function
- add basic backbone and protein keyword
- allow withspace in parans
2022-09-07 18:06:15 -07:00
Alexander Rose
32080ce918 improve jmol transpiler
- add resno ranges
- add bracketed resnames
- allow comma as OR
2022-09-07 15:16:28 -07:00
Alexander Rose
aedb2138c8 Merge pull request #548 from molstar/interactions-parent-display
add parentDisplay param to interactions repr
2022-09-06 12:19:25 -07:00
Alexander Rose
90e6938f1c Merge branch 'master' into interactions-parent-display 2022-09-06 11:57:02 -07:00
David Sehnal
eadff35250 Merge pull request #547 from molstar/sifts-alignment/residue-test
alignAndSuperposeWithSIFTSMapping includeResidueTest option
2022-09-05 15:11:53 +02:00
Alexander Rose
487450ec64 show only 'between' interaction in docking-viewer 2022-09-04 22:41:35 -07:00
Alexander Rose
f2f730bab5 fix parentDisplay 'between' logic 2022-09-04 22:41:06 -07:00
Alexander Rose
ceecee37a7 add parentDisplay param to interactions repr 2022-09-02 22:19:45 -07:00
dsehnal
394377bea9 alignAndSuperposeWithSIFTSMapping update 2022-09-02 15:50:58 +02:00
dsehnal
2b47818deb alignAndSuperposeWithSIFTSMapping includeResidueTest option 2022-09-02 15:38:06 +02:00
giagitom
9f72465052 remove unnecessary 2022-09-02 10:21:56 +02:00
giagitom
ac33b4a322 Adding distanceToVec in sphere3d namespace 2022-09-02 10:20:51 +02:00
giagitom
911c844e54 Remove logs and unused variables 2022-09-01 18:09:04 +02:00
giagitom
12b9655565 adding nearest and distance to point methods to lookup3d 2022-09-01 17:41:09 +02:00
dsehnal
2935717a06 Canvas3DParams fix 2022-08-29 16:49:30 +02:00
dsehnal
2c8d2cfa21 3.16.0 2022-08-25 17:55:54 +02:00
dsehnal
d67c0eb757 ViewportHelpContent fix 2022-08-25 17:53:10 +02:00
giagitom
7bcbcd5a7f Exposed dpoitIterations parameter 2022-08-25 16:07:36 +02:00
giagitom
1c06e7f36e Merge branch 'master' into dpoit 2022-08-24 18:44:30 +02:00
giagitom
407297adc0 added dpoit-enable query string 2022-08-24 18:21:34 +02:00
giagitom
b082b31562 Remove logs, changelog, set wboit as default option 2022-08-24 18:19:59 +02:00
giagitom
fcbeb0f82f Add defines for direct-volume 2022-08-24 17:42:08 +02:00
dsehnal
7094f8f265 Improve Viewer theming & label customization 2022-08-24 15:26:24 +02:00
giagitom
48aaa13420 Fix missing texture format 2022-08-24 15:24:30 +02:00
giagitom
d2434cf91f Merge branch 'master' into dpoit 2022-08-24 15:09:13 +02:00
giagitom
8dbe0d2793 Lint-fix 2022-08-24 14:37:33 +02:00
giagitom
7b308cf984 Keep wboit as default OIT method 2022-08-24 14:34:44 +02:00
giagitom
520af504aa Throw an error if wboit and dpoit are both enabled 2022-08-24 14:29:40 +02:00
giagitom
4bee130599 Added credits and contributions + lint-fix 2022-08-24 14:12:12 +02:00
dsehnal
19a9ed3e19 3.15.0 2022-08-23 19:00:36 +02:00
dsehnal
0dac1b93ae changelog 2022-08-23 18:57:45 +02:00
dsehnal
e824863de1 lint 2022-08-23 18:56:07 +02:00
David Sehnal
9ff8becd62 Merge pull request #530 from midlik/volume-streaming-camera-target
Volume streaming camera target (without caching)
2022-08-23 18:53:11 +02:00
Adam Midlik
fcaa1bcfa8 Merge branch 'master' into volume-streaming-camera-target 2022-08-23 18:39:37 +02:00
giagitom
39f51bcc4f Integration of Dual depth peeling - OIT method 2022-08-23 17:09:53 +02:00
Adam Midlik
1b904ee2c9 Addressed PR comments 2022-08-23 17:03:32 +02:00
yakomaxa
e2baafc426 lint 2022-08-23 18:13:14 +09:00
yakomaxa
6dabe73002 Update pymol keywords and properties 2022-08-23 17:16:10 +09:00
David Sehnal
d9b2b99c86 Merge pull request #531 from molstar/safari-wboit
add missing depth renderbuffer to wboit pass
2022-08-23 09:40:27 +02:00
Alexander Rose
bdf23a7c4e add missing depth renderbuffer to wboit pass
- fix wboit in Safari >=15
2022-08-22 23:39:50 -07:00
yakomaxa
c1723e0806 lint 2022-08-21 21:59:10 +09:00
yakomaxa
93590bd482 Remove comment line in vmd keyword 2022-08-21 21:54:38 +09:00
yakomaxa
fbaa9d9e58 Finally enabled selection by negative-valued residue index in RasMol 2022-08-21 21:48:47 +09:00
yakomaxa
ba78a8558c Finally enabled selection by negative-valued residue index 2022-08-21 21:39:20 +09:00
yakomaxa
513be04352 lint PyMOL 2022-08-21 21:38:43 +09:00
yakomaxa
15317aa11b give pymol the ability to deal with negatively indexed residue numbering 2022-08-21 19:37:20 +09:00
yakomaxa
93e107f333 give pymol the ability to deal with negatively indexed residue numbering 2022-08-21 19:32:45 +09:00
yakomaxa
655b334b0a Updated rasmol parser and spec 2022-08-21 17:14:29 +09:00
yakomaxa
95cc1c58a6 remove comment and unused character and functions 2022-08-21 16:56:20 +09:00
yakomaxa
0511d3e599 Unparenthesized residue range enabled in RasMol and parenthesized was un-activated 2022-08-21 16:51:38 +09:00
yakomaxa
92a41b5c46 Unparenthesized residue range enabled in RasMol, which should be ported to Jmol 2022-08-21 16:48:41 +09:00
yakomaxa
be16837c8c Remove unused lines 2022-08-21 15:51:41 +09:00
yakomaxa
bf5f26cb12 Remove comment-out lines. Moved un-bracketed residue name to the supported feature 2022-08-21 15:44:09 +09:00
yakomaxa
ccbcef7eff lint 2022-08-21 15:33:49 +09:00
yakomaxa
d02a97b7f0 un-bracketed residue-name and un-parenthesized residue number is enabeled in RasMol 2022-08-21 15:31:48 +09:00
yakomaxa
e0ca413c54 Remove needless substitutions to a temporary variable x 2022-08-21 10:42:32 +09:00
yakomaxa
a272fc1c05 Remove comment-out lines 2022-08-21 10:39:44 +09:00
yakomaxa
2c3f74d4ea Remove a comment-out line 2022-08-21 10:38:40 +09:00
yakomaxa
c65b2fc0fd Remove a comment-out line 2022-08-21 10:37:25 +09:00
yakomaxa
fd725adf27 Added TODO comment for comment-out functions 2022-08-21 10:36:47 +09:00
yakomaxa
ceba6da91f Remove comment-out lines 2022-08-21 10:27:57 +09:00
yakomaxa
064e7d42ee Remove a comment-out import 2022-08-21 10:26:56 +09:00
yakomaxa
cfdbf0c614 Remove comment-out lines 2022-08-21 10:25:33 +09:00
yakomaxa
436777fe34 Remove a comment-out import and a comment-out piece of code 2022-08-21 10:22:45 +09:00
yakomaxa
f08aa46222 Remove two comment-out import 2022-08-21 10:20:58 +09:00
yakomaxa
e099ac514a Remove three comment-out console.log 2022-08-21 10:19:56 +09:00
yakomaxa
873755f619 Remove comment-out unused import 2022-08-21 10:18:49 +09:00
yakomaxa
2094b7cf83 Update changelog mentioning this feature 2022-08-21 10:16:49 +09:00
yakomaxa
6ffdd81bb1 remove commented console.log 2022-08-21 10:14:32 +09:00
yakomaxa
a69cb17602 remove default paramerter and added whitespace before } 2022-08-21 10:12:56 +09:00
yakomaxa
7c82a9fd6e update transpile.ts 2022-08-21 10:11:59 +09:00
yakomaxa
10d9a6c83d removed console.log in try and replaced it by console.error in catch 2022-08-21 10:01:56 +09:00
Adam Midlik
f6ed650ef6 Refactoring, cleaning 2022-08-19 15:17:50 +02:00
Adam Midlik
df9ce6add9 Volume Streaming Around Camera - parameter Radius is relative to FOV 2022-08-19 12:42:10 +02:00
Adam Midlik
28ee47d501 Volume Streaming Around Camera - avoid chain-reaction updates 2022-08-19 11:29:52 +02:00
Adam Midlik
df2da479ad Volume Streaming - corrected help text for Detail Level (range is 1-7) 2022-08-19 10:23:00 +02:00
Adam Midlik
46eb9d8baf Volume Streaming - correct init value of Selection Detail and Dynamic Detail when View type changes 2022-08-19 10:21:08 +02:00
Adam Midlik
b6be871a21 Volume Streaming all types use MonoQueue 2022-08-18 23:28:11 +02:00
Adam Midlik
ce2367fc0a Volume streaming Around Focus uses MonoQueue (i.e. never queuing more than 1 update) 2022-08-18 19:27:53 +02:00
yakomaxa
7789e8cfea deleted package-lock.json 2022-08-18 19:58:30 +09:00
yakomaxa
51a9effcaa Added a sentence on transpilers in CHANGELOG.md 2022-08-17 12:07:12 +09:00
yakomaxa
fc3b953a8e Added whitespace remover to prefixRemoveKet in helper.ts and update rasmol.spec.ts 2022-08-17 11:55:45 +09:00
yakomaxa
a2ded3cecc Small update for RasMol parser 2022-08-17 10:28:39 +09:00
yakomaxa
ffb1390b51 added conditional evalation to inComplement 2022-08-17 10:15:18 +09:00
yakomaxa
3b92591c05 added conditional evaluation to disjunct and invert 2022-08-17 10:08:15 +09:00
yakomaxa
f173ddcf00 removed comment-out lines from rasmol parser.ts 2022-08-16 22:50:35 +09:00
yakomaxa
f78306f624 removed unused abbr. from rasmol operators.ts 2022-08-16 22:41:18 +09:00
yakomaxa
9852c9301e Changed list name in respective examples.ts and now examples.spec.ts runs without error 2022-08-16 20:07:44 +09:00
yakomaxa
2e4f3de604 debugging example.spec.ts 2022-08-16 19:47:11 +09:00
yakomaxa
300dd97353 Added spec, debugged Jmol parser, removed unused definition from RasMol properties/operators 2022-08-16 19:34:13 +09:00
yakomaxa
8e29ade83a minor change of language name: Rasmol -> RasMol 2022-08-16 16:52:34 +09:00
Alexander Rose
971c770f6a add experimental warning for transpiled scripts 2022-08-15 22:03:24 -07:00
yakomaxa
7d31a06ae4 Updated atom-set.ts according to suggestion by dsehnal 2022-08-16 12:50:02 +09:00
KoyaS
c5319ad7b1 Update package.json
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:38:31 +09:00
KoyaS
f8bdb28ea6 Update package.json
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:38:18 +09:00
KoyaS
2f8806d7c2 Update src/mol-script/runtime/query/table.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:37:55 +09:00
KoyaS
7d0a181c12 Update src/mol-model/structure/query/queries/atom-set.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:37:46 +09:00
KoyaS
27cb7e53ed Update src/mol-script/runtime/query/table.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:37:36 +09:00
KoyaS
ee5154b510 Update src/mol-script/runtime/query/table.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:37:20 +09:00
KoyaS
080837201a Update src/mol-script/runtime/query/table.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:36:44 +09:00
KoyaS
656e6c0d94 Update src/mol-script/runtime/query/table.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:36:17 +09:00
KoyaS
b018f61bab Update src/mol-script/runtime/query/table.ts
conditional evaluation

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-08-16 12:35:37 +09:00
yakomaxa
b03b306848 Enabling some numerical properties and cleaning by lint 2022-08-16 06:17:58 +09:00
yakomaxa
19bf5c2b3e [SER]3:A.CA type of selection enabled 2022-08-16 05:27:30 +09:00
yakomaxa
c22a716cf9 Update helper.ts for RasMol 2022-08-16 04:35:26 +09:00
yakomaxa
220c65da92 Parentheses re-enabled in RasMol 2022-08-16 04:27:17 +09:00
yakomaxa
675f4b86f8 Minor update of RasMol mode 2022-08-16 04:18:00 +09:00
yakomaxa
d31b3522b2 Added myself as author in package.json. Thanks. 2022-08-15 21:12:22 +09:00
yakomaxa
4ed2bab1d7 cleaning by lint 2022-08-15 20:56:20 +09:00
yakomaxa
a572872806 Updated author information and removed notes 2022-08-15 20:53:47 +09:00
yakomaxa
e3ca23db0b Remove needless setting of class members and added revised author information 2022-08-15 20:24:23 +09:00
KoyaS
67eb16a53f Merge pull request #18 from yakomaxa/rasmol
Merge from RasMol branch and begin answering the reviewers
2022-08-15 20:12:23 +09:00
yakomaxa
d7421cd1a3 Removed unwanted file 2022-08-15 20:00:48 +09:00
yakomaxa
c2e68ced66 cleaning by lint 2022-08-15 19:39:54 +09:00
yakomaxa
10cf0db050 Within operator was implemented in RasMol 2022-08-15 19:38:14 +09:00
yakomaxa
08f1a1dcfe Added experimental within to rasmol 2022-08-15 18:03:16 +09:00
yakomaxa
11bf352295 Added experimental within to rasmol 2022-08-15 15:39:57 +09:00
yakomaxa
5fd560b30a Added experimental within to rasmol 2022-08-15 15:38:38 +09:00
KoyaS
e6d01ca246 Merge pull request #17 from yakomaxa/molql_integration_PR
Molql integration pr
2022-08-15 13:03:55 +09:00
Alexander Rose
0ddf2fa00d typing tweaks 2022-08-14 14:37:30 -07:00
Alexander Rose
8776143ec2 revert to mol-script in StructureSelectionFromScript 2022-08-14 14:07:39 -07:00
Alexander Rose
080c8e7af3 add basic scipt language selector 2022-08-14 14:06:12 -07:00
yakomaxa
64998e762b cleaning by lint and adding comments 2022-08-14 11:15:50 +09:00
yakomaxa
b508da5ccc cleaning by lint 2022-08-14 11:06:24 +09:00
KoyaS
84a492655a Merge pull request #16 from yakomaxa/molql_integration_PR
Molql integration pr
2022-08-14 10:57:07 +09:00
yakomaxa
9b9cfe4138 rasmol -> pymol 2022-08-14 10:55:11 +09:00
yakomaxa
f362a7086a rasmol update 2022-08-14 10:54:31 +09:00
KoyaS
9e9ec57a5f Merge pull request #14 from yakomaxa/rasmol
merge from rasmol branch
2022-08-14 04:09:15 +09:00
yakomaxa
da6a153985 Construction of rasmol selection command has nearly finished 2022-08-14 04:03:48 +09:00
yakomaxa
b4bde3f510 Rasmol parser WIP 5 2022-08-14 03:31:08 +09:00
yakomaxa
8a5cebd635 Rasmol parser WIP 4 2022-08-14 03:23:54 +09:00
yakomaxa
ddf2733d3c Rasmol parser WIP 3 2022-08-14 00:36:37 +09:00
yakomaxa
cf65bfbcd0 Rasmol parser WIP 2 2022-08-13 23:03:26 +09:00
yakomaxa
03cce830bc Rasmol parser WIP 2022-08-13 21:43:05 +09:00
yakomaxa
913cf4c3e9 developing rasmol residue-range selector WIP 2022-08-13 12:44:14 +09:00
yakomaxa
2ccce0beaf developing rasmol residue-range selector WIP 2022-08-13 12:43:07 +09:00
yakomaxa
52239f71cd refined rasmol keywords 2022-08-13 11:20:41 +09:00
KoyaS
d9265af2e8 Merge pull request #13 from yakomaxa/molql_integration_PR
Added transpilers/_spec and checked that they pass the test
2022-08-13 08:57:48 +09:00
yakomaxa
5877f6a627 Added transpilers/_spec and checked that they pass the test 2022-08-13 02:43:54 +09:00
KoyaS
e3e982c051 Merge pull request #11 from yakomaxa/molql_integration_PR
merge from molql_integration_PR
2022-08-12 18:07:23 +09:00
yakomaxa
17f09ff3de Cleaning and lint 2022-08-12 18:00:45 +09:00
yakomaxa
7a73416c03 Cleaning and lint 2022-08-12 17:44:29 +09:00
yakomaxa
0f799d44ad Found a bug in withSameAtomProperties in filter.ts, where propSet and currentProps are placed inversely in isSuperSet() 2022-08-12 16:08:42 +09:00
yakomaxa
24ebd44f87 remove namedAtomProperty from vmd/parser.ts, removed as any from definition of withSameAtomProperties, un-commented out the continue line in filter.ts 2022-08-12 10:06:44 +09:00
KoyaS
572b10e655 Merge pull request #9 from yakomaxa/molql_integration_PR
merge from molql_integration_pr branch
2022-08-12 07:11:15 +09:00
yakomaxa
60361c176b Updated pick function in filter.ts. This enebles VMD keywords like backbone and many more. 2022-08-12 06:29:13 +09:00
yakomaxa
b232a2c58f Updated atom-set.ts and filter.ts, which leads to correct behavior of inorganic in PyMOL. The bound_to operator was found to behave differently from original PyMOL even in MolQL implementation: it's just mistake in logic. 2022-08-12 06:19:20 +09:00
yakomaxa
2a44ac56fb cleaning by lint 2022-08-12 01:18:56 +09:00
yakomaxa
d0340a3257 The function called by filter.withSameAtomProperties is debugged and almost everything works fine now. 2022-08-11 23:54:57 +09:00
yakomaxa
e708a53ddb atom-set.ts table.ts updates 2022-08-11 22:11:15 +09:00
yakomaxa
23cdd70198 updated atomCount and many problem solved 2022-08-11 21:20:55 +09:00
yakomaxa
ba4bc30a78 remove trash file 2022-08-11 14:28:31 +09:00
yakomaxa
e516ea146d Update for atomCount and countQuery in table.ts 2022-08-11 14:26:10 +09:00
yakomaxa
61af638fe4 Update atom-set 2022-08-11 13:59:56 +09:00
yakomaxa
7a0af4142f Now PyMOL within operator works. The key was to replace xs['max-radius'] -> xs['max-radius'](ctx) in table.ts 2022-08-11 10:52:56 +09:00
yakomaxa
1aa8d596a3 Updates for queryInSelection in src/mol-script/runtime/query/table.ts was reverted (made bug) 2022-08-11 10:30:09 +09:00
yakomaxa
a457810623 queryInSelection in src/mol-script/runtime/query/table.ts was updated 2022-08-11 10:18:16 +09:00
yakomaxa
4bccb7ab84 VMD jmol rasmol parser debugged 2022-08-11 09:46:15 +09:00
yakomaxa
fcd5b2ce0a VMD parser debugged 2022-08-11 09:36:32 +09:00
yakomaxa
57a1184a16 VMD parser updated, now math function to value comparison enabled 2022-08-11 09:22:37 +09:00
Adam Midlik
ef176efed8 Volume streaming 'Around Camera' - prototype 2022-08-11 01:39:22 +02:00
yakomaxa
9943e0317d VMD jmol and rasmol parser updated 2022-08-11 08:39:21 +09:00
yakomaxa
ae11c1c904 VMD keyword update 2022-08-11 03:33:28 +09:00
yakomaxa
f937e069ca Clearning by Lint 2022-08-11 02:41:43 +09:00
yakomaxa
c9326da47b Implemented subset of atomSet: atomCount, queryCount and propertySet, which enables some of PyMOL operators. Implementation should be reviewed as byring is not working, and inorganic working incorrectly 2022-08-11 02:32:45 +09:00
yakomaxa
4698c05f9c re-implementation of VMD's exwithin without using wihtin 2022-08-10 21:41:03 +09:00
yakomaxa
15fd2cd5a0 let transpile.ts show what the query is 2022-08-10 21:27:25 +09:00
yakomaxa
193eb11095 Set some operators that lacks corresponding implementation to isUnsupported : true 2022-08-10 16:34:35 +09:00
yakomaxa
59cc0096cd fixing typo in example of pymol operator: resname CA -> name CA 2022-08-10 16:28:51 +09:00
yakomaxa
6bd7390eb8 Debbuged asAtoms in helper.ts, which enables operation using this function such as byresidue and neighbor. In addition, min-spelling in neighbor operation fixed: neighbour -> neighbor 2022-08-10 16:21:06 +09:00
yakomaxa
eabeb18f34 pymol 2022-08-10 16:11:54 +09:00
yakomaxa
4b6f539ba3 debugging wrapValue in helper.ts : property.head -> property.head.name : This enables secondary structure selection for VMD via valuesTest in helper.ts 2022-08-09 22:22:08 +09:00
yakomaxa
ea55fc5f41 debugging VMD mode 2022-08-09 22:05:04 +09:00
yakomaxa
94787229a4 testLevel in helper.ts was debuged and now comes back in vmd/parser.ts 2022-08-09 19:24:34 +09:00
KoyaS
0cb162022c Merge pull request #6 from yakomaxa/enable_not_and
Enabled NOT operation
2022-08-07 17:45:45 +09:00
yakomaxa
5ff2ca311e structure-query.generator.query-in-selection was implemented: this enables NOT operation. In addition, tranpiler/helper.ts was fixed to use B.struct.generator.all() instead of B.struct.generator.atomGroups(). B.struct.generator.atomGroups() is not working for some reason, which was also met in the keyword 'all' in all of vmd/pymol/jmol parser.ts 2022-08-07 17:42:15 +09:00
KoyaS
44b8d452a8 Merge pull request #5 from yakomaxa/enable_not_and
structure-query.modifier.intersectby was implemented
2022-08-07 15:55:08 +09:00
yakomaxa
82ccb1ab23 structure-query.modifier.intersectBy was implemented: this enables AND operation 2022-08-07 15:51:06 +09:00
yakomaxa
feb8b94e30 modified _spec: I don't understand what these _spec things do 2022-08-07 10:19:03 +09:00
yakomaxa
5adc73e277 removed unsused modules from package.json 2022-08-07 10:18:41 +09:00
KoyaS
c4ac983fc7 Merge pull request #4 from molstar/master
merge from head repository
2022-08-07 08:40:03 +09:00
yakomaxa
5ba7ba3aac Cleaning before PR. Note that many tests fail. 2022-08-07 08:34:22 +09:00
yakomaxa
e879479b3d removed mock-extension for language selector and reverted src/mol-plugin-state/transforms/representation.ts to the original 2022-08-07 07:36:39 +09:00
yakomaxa
7b49463297 NamedAtomProperties was added to vmd parser 2022-08-07 06:16:29 +09:00
yakomaxa
66600c3373 deleted unused comment lines in monadic-parser.ts 2022-08-07 02:39:22 +09:00
yakomaxa
19401c4bc6 Activated RangeListProperty in vmd/parser.ts with hardcoded 'atom-test' line 2022-08-07 02:38:21 +09:00
yakomaxa
bfc8660c5e replaced structure. -> structure-query. in transpiler directory 2022-08-07 02:21:52 +09:00
yakomaxa
6a83dc56ba reverted vmd parser and properties to original 2022-08-07 02:12:21 +09:00
KoyaS
82df9d8cad Merge pull request #3 from yakomaxa/rasmol
Rasmol transpiler added
2022-08-05 23:47:28 +09:00
yakomaxa
dd30fef078 Added many properties and keywords for PyMOL and rasmol 2022-08-05 23:38:41 +09:00
yakomaxa
79feb5a1cc rasmol branch launched 2022-08-05 20:48:16 +09:00
yakomaxa
0665524b11 refined VMD: RangeListProperty in vmd/parser.ts is not working for unknown reasons, so it was substituted by NamedAtomProperties: This limits the selection like 'residue 10 to 50' and 'residue 10' 2022-08-05 20:36:32 +09:00
yakomaxa
d45367e840 Refining VMD parser and properties 2022-08-05 13:32:46 +09:00
yakomaxa
cb0d988efc Updated language-select/ui, refactored transpile.ts and monadic-parser.ts 2022-08-03 22:52:07 +09:00
yakomaxa
fc0c556967 updated default value for Script and added mock-up for language-selector 2022-08-03 21:31:53 +09:00
yakomaxa
00970164db Debugged all in jmol 2022-08-03 03:24:35 +09:00
yakomaxa
7c3d76e9fe Merge branch 'master' of github:yakomaxa/molstar_my 2022-08-03 02:50:43 +09:00
yakomaxa
190c1f9620 Added pymol, jmol and vmd alongside mol-script: pymol is now enabled 2022-08-03 02:49:52 +09:00
KoyaS
f532325147 Merge pull request #2 from yakomaxa/PyMOL
added all and polymer.protein to keywords of pymol
2022-08-02 13:25:09 +09:00
yakomaxa
278dcb8808 added all and polymer.protein to keywords of pymol 2022-08-02 13:10:42 +09:00
KoyaS
309c25e10b Merge pull request #1 from yakomaxa/jmol
Added Jmol transpiler (not enabled)
2022-07-31 23:05:06 +09:00
yakomaxa
6df728ea3e added jmol transplier (not enabled) 2022-07-31 23:01:11 +09:00
yakomaxa
dcf4ef6d74 added jmol transpiler 2022-07-31 22:49:20 +09:00
yakomaxa
4de1369a5a added jmol transpiler 2022-07-31 22:46:58 +09:00
yakomaxa
2ccfdb1280 added _spec (not tested whether it works) 2022-07-31 21:58:43 +09:00
yakomaxa
9fbf800639 added _spec (not tested whether it works) 2022-07-31 21:58:09 +09:00
yakomaxa
577daf64df PyMOL mode was successfully enabled (though mol-script is disabled...) 2022-07-31 12:27:15 +09:00
yakomaxa
0b1943e9b3 Now it worksgit add src/mol-script/transpile.ts 2022-07-31 12:17:25 +09:00
yakomaxa
30bd2dd876 Compiles but not boot 2022-07-31 12:04:38 +09:00
yakomaxa
cecd4d4179 Compiles but not working again 2022-07-31 11:15:58 +09:00
yakomaxa
364baab18d Added transpile.ts and all.ts 2022-07-31 10:30:37 +09:00
yakomaxa
bb3d4d2171 Added default export 2022-07-31 10:00:36 +09:00
yakomaxa
2355faf899 compiles but not working 2022-07-31 01:54:27 +09:00
yakomaxa
858e0b24ff now with few errors 2022-07-31 00:03:22 +09:00
yakomaxa
f7d0ed3988 Added additional modules 2022-07-30 23:17:21 +09:00
ludovic autin
9e69f5dcfa Merge branch 'master' of https://github.com/corredD/molstar-proto into forkdev
# Conflicts:
#	CHANGELOG.md
2022-07-05 08:51:25 -07:00
ludovic autin
409ed9ad67 Merge branch 'forkdev' of https://github.com/corredD/molstar-proto into forkdev
# Conflicts:
#	CHANGELOG.md
2022-06-28 08:11:29 -07:00
ludovic autin
06e78e19d0 Merge branch 'forkdev' of https://github.com/corredD/molstar-proto into forkdev 2022-05-31 09:55:03 -07:00
ludovic autin
9a1d746170 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-11-17 10:06:49 -08:00
ludovic autin
ef87ce3507 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-11-10 14:35:24 -08:00
ludovic autin
2b9053eac4 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-11-01 10:21:08 -07:00
ludovic autin
116ef719be Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-09-20 09:17:09 -07:00
297 changed files with 45578 additions and 3171 deletions

10
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,10 @@
<!-- Thank you for contributing to Mol* -->
# Description
## Actions
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
- [ ] Updated headers of modified files
- [ ] Added my name to `package.json`'s `contributors`

View File

@@ -6,7 +6,6 @@
"recommendations": [
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner",
"msjsdiag.debugger-for-chrome",
"slevesque.shader",
"stpn.vscode-graphql",
"wayou.vscode-todo-highlight"

View File

@@ -6,6 +6,127 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.28.0] - 2022-12-20
- Show histogram in direct volume control point settings
- Add `solidInterior` parameter to sphere/cylinder impostors
- [Breaking] Tweak `ignoreHydrogens` non-polar handling (introduced in 3.27.0)
- Add `meshes` and `volumes-and-segmentations` extensions
- Fix missing support for info in `ParamDefinition.Converted`
- Add support for multi-visual volume representations
- Improve volume isosurface bounding-sphere
- Add basic volume segmentation support to core
- Add `Volume.Segment` model
- Add `Segmentation` custom volume property
- Add `SegmentRepresentation` representation
- Add `volume-segment` color theme
- Fix GPU marching cubes failing for large meshes with webgl2 (due to use of float16)
## [v3.27.0] - 2022-12-15
- Add an `includeTransparent` parameter to hide/show outlines of components that are transparent
- Fix 'once' for animations of systems with many frames
- Better guard against issue (black fringes) with bumpiness in impostors
- Improve impostor shaders
- Fix sphere near-clipping with orthographic projection
- Fix cylinder near-clipping
- Add interior cylinder caps
- Add per-pixel object clipping
- Fix `QualityAssessment` assignment bug for structures with different auth vs label sequence numbering
- Refresh `ApplyActionControl`'s param definition when toggling expanded state
- Fix `struct_conn` bond assignment for ions
- Ability to show only polar hydrogens
## [v3.26.0] - 2022-12-04
- Support for ``powerPreference`` webgl attribute. Add ``PluginConfig.General.PowerPreference`` and ``power-preference`` Viewer GET param.
- Excluded common protein caps `NME` and `ACE` from the ligand selection query
- Add screen-space shadow post-processing effect
- Add "Structure Molecular Surface" visual
- Add `external-volume` theme (coloring of arbitrary geometries by user-selected volume)
## [v3.25.1] - 2022-11-20
- Fix edge-case in `Structure.eachUnitPair` with single-element units
- Fix 'auto' structure-quality for coarse models
## [v3.25.0] - 2022-11-16
- Fix handling of gzipped assets (reverts #615)
## [v3.24.0] - 2022-11-13
- Make `PluginContext.initContainer` checkered canvas background optional
- Store URL of downloaded assets to detect zip/gzip based on extension (#615)
- Add optional `operator.key`; can be referenced in `IndexPairBonds`
- Add overpaint/transparency/substance theme strength to representations
- Fix viewport color for transparent background
## [v3.23.0] - 2022-10-19
- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
- Add `PluginContext.canvas3dInitialized`
- `createPluginUI` now resolves after the 3d canvas has been initialized
- Change EM Volume Streaming default from `Whole Structure` to `Auto`
## [v3.22.0] - 2022-10-17
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
## [v3.21.0] - 2022-10-17
- Add `VolumeIsosurfaceParams.pickingGranularity` param
- Prevent component controls collapsing when option is selected
## [v3.20.0] - 2022-10-16
- [Breaking] Rename the ``model-index`` color theme to ``trajectory-index``
- Add a new ``model-index`` color theme that uniquely colors each loaded model
- Add the new ``model-index`` and ``structure-index`` color themes as an option for the carbon color in the ``element-symbol`` and ``ilustrative`` color themes
- Add ``structure-index`` color theme that uniquely colors each root structure
- Add ``nearest`` method to ``Lookup3D``
- Add mipmap-based blur for skybox backgrounds
## [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

28000
examples/long_animation.sdf Normal file

File diff suppressed because it is too large Load Diff

5485
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.14.0",
"version": "3.28.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -89,71 +89,76 @@
"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>",
"Jason Pattle <jpattle@exscientia.co.uk>",
"David Williams <dwilliams@nobiastx.com>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.2.1",
"@graphql-codegen/cli": "^2.11.6",
"@graphql-codegen/time": "^3.2.1",
"@graphql-codegen/typescript": "^2.7.3",
"@graphql-codegen/add": "^3.2.3",
"@graphql-codegen/cli": "^2.16.1",
"@graphql-codegen/time": "^3.2.3",
"@graphql-codegen/typescript": "^2.8.5",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
"@graphql-codegen/typescript-graphql-request": "^4.5.3",
"@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/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"@graphql-codegen/typescript-graphql-request": "^4.5.8",
"@graphql-codegen/typescript-operations": "^2.5.10",
"@types/cors": "^2.8.13",
"@types/gl": "^6.0.2",
"@types/jest": "^29.2.4",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"benchmark": "^2.1.4",
"concurrently": "^7.3.0",
"concurrently": "^7.6.0",
"cpx2": "^4.2.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"eslint": "^8.22.0",
"css-loader": "^6.7.3",
"eslint": "^8.30.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.1.0",
"fs-extra": "^11.1.0",
"graphql": "^16.6.0",
"http-server": "^14.1.1",
"jest": "^28.1.3",
"mini-css-extract-plugin": "^2.6.1",
"jest": "^29.3.1",
"mini-css-extract-plugin": "^2.7.2",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.54.5",
"sass-loader": "^13.0.2",
"simple-git": "^3.12.0",
"sass": "^1.57.0",
"sass-loader": "^13.2.0",
"simple-git": "^3.15.1",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^28.0.8",
"typescript": "^4.7.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
"ts-jest": "^29.0.3",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"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.15",
"@types/node": "^16.18.10",
"@types/node-fetch": "^2.6.2",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
"body-parser": "^1.20.0",
"body-parser": "^1.20.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.18.1",
"express": "^4.18.2",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.15",
"immer": "^9.0.16",
"immutable": "^4.1.0",
"node-fetch": "^2.6.7",
"rxjs": "^7.5.6",
"swagger-ui-dist": "^4.14.0",
"tslib": "^2.4.0",
"rxjs": "^7.8.0",
"swagger-ui-dist": "^4.15.5",
"tslib": "^2.4.1",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"
},

View File

@@ -58,20 +58,22 @@ class Viewer {
}
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
const o = {
...DefaultViewerOptions, ...{
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
viewportShowExpand: true,
viewportShowControls: false,
viewportShowSettings: false,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
viewportShowExpand: true,
viewportShowControls: false,
viewportShowSettings: false,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
}
};
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
@@ -135,18 +137,16 @@ class Viewer {
}
};
plugin.behaviors.canvas3d.initialized.subscribe(v => {
if (v) {
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
backgroundColor: ColorNames.white,
},
camera: {
...plugin.canvas3d!.props.camera,
helper: { axes: { name: 'off', params: {} } }
}
} });
PluginCommands.Canvas3D.SetSettings(plugin, {
settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
backgroundColor: ColorNames.white,
},
camera: {
...plugin.canvas3d!.props.camera,
helper: { axes: { name: 'off', params: {} } }
}
}
});
@@ -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');

View File

@@ -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>
*/
@@ -31,7 +31,8 @@ function shinyStyle(plugin: PluginContext) {
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'off', params: {} },
outline: { name: 'off', params: {} }
shadow: { name: 'off', params: {} },
outline: { name: 'off', params: {} },
}
} });
}
@@ -48,13 +49,15 @@ function occlusionStyle(plugin: PluginContext) {
blurKernelSize: 15,
radius: 5,
samples: 32,
resolutionScale: 1
resolutionScale: 1,
} },
outline: { name: 'on', params: {
scale: 1.0,
threshold: 0.33,
color: Color(0x0000),
} }
includeTransparent: true,
} },
shadow: { name: 'off', params: {} },
}
} });
}
@@ -202,14 +205,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' }),
};

View File

@@ -9,6 +9,7 @@ import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Volseg, VolsegVolumeServerConfig } from '../../extensions/volumes-and-segmentations';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
@@ -56,6 +57,7 @@ const CustomFormats = [
];
const Extensions = {
'volseg': PluginSpec.Behavior(Volseg),
'backgrounds': PluginSpec.Behavior(Backgrounds),
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
@@ -88,7 +90,10 @@ 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,
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -102,6 +107,7 @@ const DefaultViewerOptions = {
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
@@ -158,7 +164,10 @@ 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.General.PowerPreference, o.powerPreference],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
@@ -173,6 +182,7 @@ export class Viewer {
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
]
};
@@ -199,7 +209,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 +218,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 +507,4 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
}
}
});
});

View File

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

View File

@@ -60,7 +60,10 @@
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';
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
molstar.Viewer.create('app', {
layoutShowControls: !hideControls,
@@ -74,8 +77,11 @@
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,
powerPreference: powerPreference || 'high-performance',
}).then(viewer => {
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);

View File

@@ -71,6 +71,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'ec-type':
case 'ucode-alphanum-csv':
case 'id_list':
case 'entity_id_list':
return ListCol('str', ',', description);
case 'id_list_spc':
return ListCol('str', ' ', description);

View File

@@ -38,7 +38,7 @@ function print(volume: Volume) {
}
async function doMesh(volume: Volume, filename: string) {
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
// Export the mesh in OBJ format.

View File

@@ -4,16 +4,16 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
<Controls orbitals={orbitals} />
</PluginContextContainer>, parent);
</PluginContextContainer>);
}
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {

View File

@@ -82,24 +82,20 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
if (!init) return;
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
});
return;
}
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
});
return;
}
mountControls(this, document.getElementById('controls')!);
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
});
mountControls(this, document.getElementById('controls')!);
}
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -25,7 +25,8 @@ const Canvas3DPresets = {
canvas3d: <Preset>{
postprocessing: {
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000), includeTransparent: true, } },
shadow: { name: 'off', params: {} },
},
renderer: {
ambientIntensity: 1.0,
@@ -37,7 +38,8 @@ const Canvas3DPresets = {
canvas3d: <Preset>{
postprocessing: {
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
outline: { name: 'off', params: {} }
outline: { name: 'off', params: {} },
shadow: { name: 'off', params: {} },
},
renderer: {
ambientIntensity: 0.4,
@@ -50,7 +52,8 @@ const Canvas3DPresets = {
canvas3d: <Preset>{
postprocessing: {
occlusion: { name: 'off', params: {} },
outline: { name: 'off', params: {} }
outline: { name: 'off', params: {} },
shadow: { name: 'off', params: {} },
},
renderer: {
ambientIntensity: 0.4,

View File

@@ -72,6 +72,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
lightness: 0,
saturation: 0,
opacity: 1,
blur: 0.3,
}
}
}, 'Purple Nebula Skybox'],

View File

@@ -585,7 +585,7 @@ export const LoadCellPackModel = StateAction.build({
... ctx.managers.structure.component.state.options,
visualQuality: 'custom',
ignoreLight: true,
showHydrogens: false,
hydrogens: 'hide-all',
});
ctx.canvas3d?.setProps({
multiSample: { mode: 'off' },
@@ -606,12 +606,22 @@ export const LoadCellPackModel = StateAction.build({
resolutionScale: 1,
}
},
shadow: {
name: 'on',
params: {
bias: 0.6,
maxDistance: 80,
steps: 3,
tolerance: 1.0,
}
},
outline: {
name: 'on',
params: {
scale: 1,
threshold: 0.33,
color: ColorNames.black,
includeTransparent: true,
}
}
}

View File

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

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
/**
* Represents a set of values to choose from, with a default value. Example:
* ```
* export const MyChoice = new Choice({ yes: 'I agree', no: 'Nope' }, 'yes');
* export type MyChoiceType = Choice.Values<typeof MyChoice>; // 'yes'|'no'
* ```
*/
export class Choice<T extends string, D extends T> {
readonly defaultValue: D;
readonly options: [T, string][];
private readonly nameDict: { [value in T]: string };
constructor(opts: { [value in T]: string }, defaultValue: D) {
this.defaultValue = defaultValue;
this.options = Object.keys(opts).map(k => [k as T, opts[k as T]]);
this.nameDict = opts;
}
PDSelect(defaultValue?: T, info?: PD.Info): PD.Select<T> {
return PD.Select<T>(defaultValue ?? this.defaultValue, this.options, info);
}
prettyName(value: T): string {
return this.nameDict[value];
}
}
export namespace Choice {
export type Values<T extends Choice<any, any>> = T extends Choice<infer R, any> ? R : any;
}

View File

@@ -0,0 +1,225 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
/** Testing examples for using mesh-extension.ts. */
import { ParseMeshlistTransformer, MeshShapeTransformer, MeshlistData } from './mesh-extension';
import * as MeshUtils from './mesh-utils';
import { BACKGROUND_OPACITY, FOREROUND_OPACITY, InitMeshStreaming } from './mesh-streaming/transformers';
import { MeshServerInfo } from './mesh-streaming/server-info';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { Download } from '../../mol-plugin-state/transforms/data';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { Box3D } from '../../mol-math/geometry';
import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { ParamDefinition } from '../../mol-util/param-definition';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { Volume } from '../../mol-model/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { Asset } from '../../mol-util/assets';
import { CIF } from '../../mol-io/reader/cif';
export const DB_URL = '/db'; // local
export async function runMeshExtensionExamples(plugin: PluginUIContext, db_url: string = DB_URL) {
console.time('TIME MESH EXAMPLES');
// await runIsosurfaceExample(plugin, db_url);
// await runMolsurfaceExample(plugin);
// Focused Ion Beam-Scanning Electron Microscopy of mitochondrial reticulum in murine skeletal muscle: https://www.ebi.ac.uk/empiar/EMPIAR-10070/
// await runMeshExample(plugin, 'all', db_url);
// await runMeshExample(plugin, 'fg', db_url);
// await runMultimeshExample(plugin, 'fg', 'worst', db_url);
// await runCifMeshExample(plugin);
// await runMeshExample2(plugin, 'fg');
await runMeshStreamingExample(plugin);
console.timeEnd('TIME MESH EXAMPLES');
}
/** Example for downloading multiple separate segments, each containing 1 mesh. */
export async function runMeshExample(plugin: PluginUIContext, segments: 'fg' | 'all', db_url: string = DB_URL) {
const detail = 2;
const segmentIds = (segments === 'all') ?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17] // segment-16 has no detail-2
: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 17]; // segment-13 and segment-15 are quasi background
for (const segmentId of segmentIds) {
await createMeshFromUrl(plugin, `${db_url}/empiar-10070-mesh-rounded/segment-${segmentId}/detail-${detail}`, segmentId, detail, true, undefined);
}
}
/** Example for downloading multiple separate segments, each containing 1 mesh. */
export async function runMeshExample2(plugin: PluginUIContext, segments: 'one' | 'few' | 'fg' | 'all') {
const detail = 1;
const segmentIds = (segments === 'one') ? [15]
: (segments === 'few') ? [1, 4, 7, 10, 16]
: (segments === 'all') ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17] // segment-16 has no detail-2
: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 17]; // segment-13 and segment-15 are quasi background
for (const segmentId of segmentIds) {
await createMeshFromUrl(plugin, `http://localhost:9000/v2/empiar/empiar-10070/mesh_bcif/${segmentId}/${detail}`, segmentId, detail, false, undefined);
}
}
/** Example for downloading a single segment containing multiple meshes. */
export async function runMultimeshExample(plugin: PluginUIContext, segments: 'fg' | 'all', detailChoice: 'best' | 'worst', db_url: string = DB_URL) {
const urlDetail = (detailChoice === 'best') ? '2' : 'worst';
const numDetail = (detailChoice === 'best') ? 2 : 1000;
await createMeshFromUrl(plugin, `${db_url}/empiar-10070-multimesh-rounded/segments-${segments}/detail-${urlDetail}`, 0, numDetail, false, undefined);
}
/** Download data and create state tree hierarchy down to visual representation. */
export async function createMeshFromUrl(plugin: PluginContext, meshDataUrl: string, segmentId: number, detail: number,
collapseTree: boolean, color?: Color, parent?: StateObjectSelector | StateObjectRef, transparentIfBboxAbove?: number,
name?: string, ownerId?: string) {
const update = parent ? plugin.build().to(parent) : plugin.build().toRoot();
const rawDataNodeRef = update.apply(Download,
{ url: meshDataUrl, isBinary: true, label: `Downloaded Data ${segmentId}` },
{ state: { isCollapsed: collapseTree } }
).ref;
const parsedDataNode = await update.to(rawDataNodeRef)
.apply(StateTransforms.Data.ParseCif)
.apply(ParseMeshlistTransformer,
{ label: undefined, segmentId: segmentId, segmentName: name ?? `Segment ${segmentId}`, detail: detail, ownerId: ownerId },
{}
)
.commit();
let transparent = false;
if (transparentIfBboxAbove !== undefined && parsedDataNode.data) {
const bbox = MeshlistData.bbox(parsedDataNode.data) || Box3D.zero();
transparent = Box3D.volume(bbox) > transparentIfBboxAbove;
}
await plugin.build().to(parsedDataNode)
.apply(MeshShapeTransformer, { color: color },)
.apply(ShapeRepresentation3D,
{ alpha: transparent ? BACKGROUND_OPACITY : FOREROUND_OPACITY },
{ tags: ['mesh-segment-visual', `segment-${segmentId}`] }
)
.commit();
return rawDataNodeRef;
}
export async function runMeshStreamingExample(plugin: PluginUIContext, source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070', serverUrl?: string, parent?: StateObjectSelector) {
const params = ParamDefinition.getDefaultValues(MeshServerInfo.Params);
if (serverUrl) params.serverUrl = serverUrl;
params.source = source;
params.entryId = entryId;
await plugin.runTask(plugin.state.data.applyAction(InitMeshStreaming, params, parent?.ref), { useOverlay: false });
}
/** Example for downloading a protein structure and visualizing molecular surface. */
export async function runMolsurfaceExample(plugin: PluginUIContext) {
const entryId = 'pdb-7etq';
// Node "https://www.ebi.ac.uk/pdbe/entry-files/download/7etq.bcif" ("transformer": "ms-plugin.download") -> var data
const data = await plugin.builders.data.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/download/7etq.bcif', isBinary: true }, { state: { isGhost: false } });
console.log('formats:', plugin.dataFormats.list);
// Node "CIF File" ("transformer": "ms-plugin.parse-cif")
// Node "7ETQ 1 model" ("transformer": "ms-plugin.trajectory-from-mmcif") -> var trajectory
const parsed = await plugin.dataFormats.get('mmcif')!.parse(plugin, data, { entryId });
const trajectory: StateObjectSelector<PluginStateObject.Molecule.Trajectory> = parsed.trajectory;
console.log('parsed', parsed);
console.log('trajectory', trajectory);
// Node "Model 1" ("transformer": "ms-plugin.model-from-trajectory") -> var model
const model = await plugin.build().to(trajectory).apply(StateTransforms.Model.ModelFromTrajectory).commit();
console.log('model:', model);
// Node "Model 91 elements" ("transformer": "ms-plugin.structure-from-model") -> var structure
const structure = await plugin.build().to(model).apply(StateTransforms.Model.StructureFromModel,).commit();
console.log('structure:', structure);
// Node "Molecular Surface" ("transformer": "ms-plugin.structure-representation-3d") -> var repr
const reprParams = createStructureRepresentationParams(plugin, undefined, { type: 'molecular-surface' });
const repr = await plugin.build().to(structure).apply(StateTransforms.Representation.StructureRepresentation3D, reprParams).commit();
console.log('repr:', repr);
}
/** Example for downloading an EMDB density data and visualizing isosurface. */
export async function runIsosurfaceExample(plugin: PluginUIContext, db_url: string = DB_URL) {
const entryId = 'emd-1832';
const isoLevel = 2.73;
let root = await plugin.build();
const data = await plugin.builders.data.download({ url: `${db_url}/emd-1832-box`, isBinary: true }, { state: { isGhost: false } });
const parsed = await plugin.dataFormats.get('dscif')!.parse(plugin, data, { entryId });
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[0] ?? parsed.volume;
const volumeData = volume.cell!.obj!.data;
console.log('data:', data);
console.log('parsed:', parsed);
console.log('volume:', volume);
console.log('volumeData:', volumeData);
root = await plugin.build();
console.log('root:', root);
console.log('to:', root.to(volume));
console.log('toRoot:', root.toRoot());
let volumeParams;
volumeParams = createVolumeRepresentationParams(plugin, volumeData, {
type: 'isosurface',
typeParams: {
alpha: 0.5,
isoValue: Volume.adjustedIsoValue(volumeData, isoLevel, 'relative'),
visuals: ['solid'],
sizeFactor: 1,
},
color: 'uniform',
colorParams: { value: Color(0x00aaaa) },
});
root.to(volume).apply(StateTransforms.Representation.VolumeRepresentation3D, volumeParams);
volumeParams = createVolumeRepresentationParams(plugin, volumeData, {
type: 'isosurface',
typeParams: {
alpha: 1.0,
isoValue: Volume.adjustedIsoValue(volumeData, isoLevel, 'relative'),
visuals: ['wireframe'],
sizeFactor: 1,
},
color: 'uniform',
colorParams: { value: Color(0x8800aa) },
});
root.to(volume).apply(StateTransforms.Representation.VolumeRepresentation3D, volumeParams);
await root.commit();
}
export async function runCifMeshExample(plugin: PluginUIContext, api: string = 'http://localhost:9000/v2',
source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070',
segmentId: number = 1, detail: number = 10,
) {
const url = `${api}/${source}/${entryId}/mesh_bcif/${segmentId}/${detail}`;
getMeshFromBcif(plugin, url);
}
async function getMeshFromBcif(plugin: PluginUIContext, url: string) {
const urlAsset = Asset.getUrlAsset(plugin.managers.asset, url); // QUESTION how is urlAsset better than normal `fetch`
const asset = await plugin.runTask(plugin.managers.asset.resolve(urlAsset, 'binary'));
const parsed = await plugin.runTask(CIF.parseBinary(asset.data));
if (parsed.isError) {
plugin.log.error('VolumeStreaming, parsing CIF: ' + parsed.toString());
return;
}
console.log('blocks:', parsed.result.blocks);
const mesh = await MeshUtils.meshFromCif(parsed.result);
console.log(mesh);
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Column, Database } from '../../mol-data/db';
import { CifFrame } from '../../mol-io/reader/cif';
import { toDatabase } from '../../mol-io/reader/cif/schema';
const int = Column.Schema.int;
const float = Column.Schema.float;
// TODO in future, move to molstar/src/mol-io/reader/cif/schema/mesh.ts
export const Mesh_Data_Schema = {
mesh: {
id: int,
},
mesh_vertex: {
mesh_id: int,
vertex_id: int,
x: float,
y: float,
z: float,
},
/** Table of triangles, 3 rows per triangle */
mesh_triangle: {
mesh_id: int,
/** Indices of vertices within mesh */
vertex_id: int,
}
};
export type Mesh_Data_Schema = typeof Mesh_Data_Schema;
export interface Mesh_Data_Database extends Database<Mesh_Data_Schema> {}
// TODO in future, move to molstar/src/mol-io/reader/cif.ts: CIF.schema.mesh
export const CIF_schema_mesh = (frame: CifFrame) => toDatabase<Mesh_Data_Schema, Mesh_Data_Database>(Mesh_Data_Schema, frame);

View File

@@ -0,0 +1,227 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
/** Defines new types of State tree transformers for dealing with mesh data. */
import { BaseGeometry, VisualQuality, VisualQualityOptions } from '../../mol-geo/geometry/base';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { CifFile } from '../../mol-io/reader/cif';
import { Box3D } from '../../mol-math/geometry';
import { Vec3 } from '../../mol-math/linear-algebra';
import { Shape } from '../../mol-model/shape';
import { ShapeProvider } from '../../mol-model/shape/provider';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransformer } from '../../mol-state';
import { Task } from '../../mol-task';
import { Color } from '../../mol-util/color';
import { Material } from '../../mol-util/material';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import * as MeshUtils from './mesh-utils';
export const VolsegTransform: StateTransformer.Builder.Root = StateTransformer.builderFactory('volseg');
// // // // // // // // // // // // // // // // // // // // // // // //
// Parsed data
/** Data type for `MeshlistStateObject` - list of meshes */
export interface MeshlistData {
segmentId: number,
segmentName: string,
detail: number,
meshIds: number[],
mesh: Mesh,
/** Reference to the object which created this meshlist (e.g. `MeshStreaming.Behavior`) */
ownerId?: string,
}
export namespace MeshlistData {
export function empty(): MeshlistData {
return {
segmentId: 0,
segmentName: 'Empty',
detail: 0,
meshIds: [],
mesh: Mesh.createEmpty(),
};
};
export async function fromCIF(data: CifFile, segmentId: number, segmentName: string, detail: number): Promise<MeshlistData> {
const { mesh, meshIds } = await MeshUtils.meshFromCif(data);
return {
segmentId,
segmentName,
detail,
meshIds,
mesh,
};
}
export function stats(meshListData: MeshlistData): string {
return `Meshlist "${meshListData.segmentName}" (detail ${meshListData.detail}): ${meshListData.meshIds.length} meshes, ${meshListData.mesh.vertexCount} vertices, ${meshListData.mesh.triangleCount} triangles`;
}
export function getShape(data: MeshlistData, color: Color): Shape<Mesh> {
const mesh = data.mesh;
const meshShape: Shape<Mesh> = Shape.create(data.segmentName, data, mesh,
() => color,
() => 1,
// group => `${data.segmentName} | Segment ${data.segmentId} | Detail ${data.detail} | Mesh ${group}`,
group => data.segmentName,
);
return meshShape;
}
export function combineBBoxes(boxes: (Box3D | null)[]): Box3D | null {
let result = null;
for (const box of boxes) {
if (!box) continue;
if (result) {
Vec3.min(result.min, result.min, box.min);
Vec3.max(result.max, result.max, box.max);
} else {
result = Box3D.zero();
Box3D.copy(result, box);
}
}
return result;
}
export function bbox(data: MeshlistData): Box3D | null {
return MeshUtils.bbox(data.mesh);
}
export function allVerticesUsed(data: MeshlistData): boolean {
const unusedVertices = new Set();
for (let i = 0; i < data.mesh.vertexCount; i++) {
unusedVertices.add(i);
}
for (let i = 0; i < 3 * data.mesh.triangleCount; i++) {
const v = data.mesh.vertexBuffer.ref.value[i];
unusedVertices.delete(v);
}
return unusedVertices.size === 0;
}
}
// // // // // // // // // // // // // // // // // // // // // // // //
// Raw Data -> Parsed data
export class MeshlistStateObject extends PluginStateObject.Create<MeshlistData>({ name: 'Parsed Meshlist', typeClass: 'Object' }) { }
// QUESTION: is typeClass just for color, or does do something?
export const ParseMeshlistTransformer = VolsegTransform({
name: 'meshlist-from-string',
from: PluginStateObject.Format.Cif,
to: MeshlistStateObject,
params: {
label: PD.Text(MeshlistStateObject.type.name, { isHidden: true }), // QUESTION: Is this the right way to pass a value to apply() without exposing it in GUI?
segmentId: PD.Numeric(1, {}, { isHidden: true }),
segmentName: PD.Text('Segment'),
detail: PD.Numeric(1, {}, { isHidden: true }),
/** Reference to the object which manages this meshlist (e.g. `MeshStreaming.Behavior`) */
ownerId: PD.Text('', { isHidden: true }),
}
})({
apply({ a, params }, globalCtx) { // `a` is the parent node, params are 2nd argument to To.apply(), `globalCtx` is the plugin
return Task.create('Create Parsed Meshlist', async ctx => {
const meshlistData = await MeshlistData.fromCIF(a.data, params.segmentId, params.segmentName, params.detail);
meshlistData.ownerId = params.ownerId;
const es = meshlistData.meshIds.length === 1 ? '' : 'es';
return new MeshlistStateObject(meshlistData, { label: params.label, description: `${meshlistData.segmentName} (${meshlistData.meshIds.length} mesh${es})` });
});
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
// Parsed data -> Shape
/** Data type for PluginStateObject.Shape.Provider */
type MeshShapeProvider = ShapeProvider<MeshlistData, Mesh, Mesh.Params>;
namespace MeshShapeProvider {
export function fromMeshlistData(meshlist: MeshlistData, color?: Color): MeshShapeProvider {
const theColor = color ?? MeshUtils.ColorGenerator.next().value;
return {
label: 'Mesh',
data: meshlist,
params: meshParamDef, // TODO how to pass the real params correctly?
geometryUtils: Mesh.Utils,
getShape: (ctx, data: MeshlistData) => MeshlistData.getShape(data, theColor),
};
}
}
/** Params for MeshShapeTransformer */
const meshShapeParamDef = {
color: PD.Value<Color | undefined>(undefined), // undefined means random color
};
const meshParamDef: Mesh.Params = {
// These are basically original MS.Mesh.Params:
// BaseGeometry.Params
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
quality: PD.Select<VisualQuality>('custom', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }), // use 'custom' when wanting to apply doubleSided
material: Material.getParam(),
clip: Mesh.Params.clip, // PD.Group(MS.Clip.Params),
instanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }),
// Mesh.Params
doubleSided: PD.Boolean(true, BaseGeometry.CustomQualityParamInfo), // default: false (set true, to show at least something in weird cases)
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory), // default: false (set true to see the real mesh vertices and triangles)
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory), // this is like better opacity (angle-dependent), nice
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
// TODO when I change values here, it has effect, but not if I change them in GUI
};
export const MeshShapeTransformer = VolsegTransform({
name: 'shape-from-meshlist',
display: { name: 'Shape from Meshlist', description: 'Create Shape from Meshlist data' },
from: MeshlistStateObject,
to: PluginStateObject.Shape.Provider,
params: meshShapeParamDef
})({
apply({ a, params }) {
// you can look for example at ShapeFromPly in mol-plugin-state/tansforms/model.ts as an example
const shapeProvider = MeshShapeProvider.fromMeshlistData(a.data, params.color);
return new PluginStateObject.Shape.Provider(shapeProvider, { label: PluginStateObject.Shape.Provider.type.name, description: a.description });
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
// Shape -> Repr
// type MeshRepr = MS.PluginStateObject.Representation3DData<MS.ShapeRepresentation<MS.ShapeProvider<any,any,any>, MS.Mesh, MS.Mesh.Params>, any>;
// export const CustomMeshReprTransformer = VolsegTransform({
// name: 'custom-repr',
// from: MS.PluginStateObject.Shape.Provider, // later we can change this
// to: MS.PluginStateObject.Shape.Representation3D,
// })({
// apply({ a }, globalCtx) {
// const repr: MeshRepr = createRepr(a.data); // TODO implement createRepr
// // have a look at MS.StateTransforms.Representation.ShapeRepresentation3D if you want to try implementing yourself
// return new MS.PluginStateObject.Shape.Representation3D(repr)
// },
// })
// export async function createMeshRepr(plugin: MS.PluginContext, data: any) {
// await plugin.build()
// .toRoot()
// .apply(CreateMyShapeTransformer, { data })
// .apply(MS.StateTransforms.Representation.ShapeRepresentation3D) // this should work
// // or .apply(CustomMeshRepr)
// .commit();
// }
// export function createRepr(reprData: MS.ShapeProvider<any,any,any>): MeshRepr {
// throw new Error('NotImplemented');
// return {} as MeshRepr;
// }

View File

@@ -0,0 +1,335 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { distinctUntilChanged, map } from 'rxjs';
import { CIF } from '../../../mol-io/reader/cif';
import { Box3D } from '../../../mol-math/geometry';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { PluginBehavior } from '../../../mol-plugin/behavior';
import { PluginCommand } from '../../../mol-plugin/command';
import { PluginCommands } from '../../../mol-plugin/commands';
import { PluginContext } from '../../../mol-plugin/context';
import { UUID } from '../../../mol-util';
import { Asset } from '../../../mol-util/assets';
import { Color } from '../../../mol-util/color';
import { ColorNames } from '../../../mol-util/color/names';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Choice } from '../choice';
import { MeshlistData } from '../mesh-extension';
import { Metadata } from '../metadata';
import { MeshServerInfo } from './server-info';
const DEFAULT_SEGMENT_NAME = 'Untitled segment';
const DEFAULT_SEGMENT_COLOR = ColorNames.lightgray;
export const NO_SEGMENT = -1;
/** Maximum (worst) detail level available in GUI (TODO set actual maximum possible value) */
const MAX_DETAIL = 10;
const DEFAULT_DETAIL = 7; // TODO decide a reasonable default
/** Segments whose bounding box volume is above this value (relative to the overall bounding box) are considered as background segments */
export const BACKGROUND_SEGMENT_VOLUME_THRESHOLD = 0.5;
// const DEBUG_IGNORED_SEGMENTS = new Set([13, 15]); // TODO remove
const DEBUG_IGNORED_SEGMENTS = new Set(); // TODO remove
export class MeshStreaming extends PluginStateObject.CreateBehavior<MeshStreaming.Behavior>({ name: 'Mesh Streaming' }) { }
export namespace MeshStreaming {
export namespace Params {
export const ViewTypeChoice = new Choice({ off: 'Off', select: 'Select', all: 'All' }, 'select'); // TODO add camera target?
export type ViewType = Choice.Values<typeof ViewTypeChoice>;
export function create(options: MeshServerInfo.Data) {
return {
view: PD.MappedStatic('select', {
'off': PD.Group({}),
'select': PD.Group({
baseDetail: PD.Numeric(DEFAULT_DETAIL, { min: 1, max: MAX_DETAIL, step: 1 }, { description: 'Detail level for the non-selected segments (lower number = better)' }),
focusDetail: PD.Numeric(1, { min: 1, max: MAX_DETAIL, step: 1 }, { description: 'Detail level for the selected segment (lower number = better)' }),
selectedSegment: PD.Numeric(NO_SEGMENT, {}, { isHidden: true }),
}, { isFlat: true }),
'all': PD.Group({
detail: PD.Numeric(DEFAULT_DETAIL, { min: 1, max: MAX_DETAIL, step: 1 }, { description: 'Detail level for all segments (lower number = better)' })
}, { isFlat: true }),
}, { description: '"Off" hides all segments. \n"Select" shows all segments in lower detail, clicked segment in better detail. "All" shows all segment in the same level.' }),
};
}
export type Definition = ReturnType<typeof create>
export type Values = PD.Values<Definition>
export function copyValues(params: Values): Values {
return {
view: {
name: params.view.name,
params: { ...params.view.params } as any,
}
};
}
export function valuesEqual(p: Values, q: Values): boolean {
if (p.view.name !== q.view.name) return false;
for (const key in p.view.params) {
if ((p.view.params as any)[key] !== (q.view.params as any)[key]) return false;
}
return true;
}
export function detailsEqual(p: Values, q: Values): boolean {
switch (p.view.name) {
case 'off':
return q.view.name === 'off';
case 'select':
return q.view.name === 'select' && p.view.params.baseDetail === q.view.params.baseDetail && p.view.params.focusDetail === q.view.params.focusDetail;
case 'all':
return q.view.name === 'all' && p.view.params.detail === q.view.params.detail;
default:
throw new Error('Not implemented');
}
}
}
export interface VisualInfo {
tag: string, // e.g. high-2, low-1 // ? remove if can be omitted
segmentId: number, // ? remove if unused
segmentName: string, // ? remove if unused
detailType: VisualInfo.DetailType, // ? remove if unused
detail: number, // ? remove if unused
color: Color, // move to MeshlistData?
visible: boolean,
data?: MeshlistData,
}
export namespace VisualInfo {
export type DetailType = 'low' | 'high';
export const DetailTypes: DetailType[] = ['low', 'high'];
export function tagFor(segmentId: number, detail: DetailType) {
return `${detail}-${segmentId}`;
}
}
export class Behavior extends PluginBehavior.WithSubscribers<Params.Values> {
private id: string;
private ref: string = '';
public parentData: MeshServerInfo.Data;
private metadata?: Metadata;
public visuals?: { [tag: string]: VisualInfo };
public backgroundSegments: { [segmentId: number]: boolean } = {};
private focusObservable = this.plugin.behaviors.interaction.click.pipe( // QUESTION is this OK way to get focused segment?
map(evt => evt.current.loci),
map(loci => (loci.kind === 'group-loci') ? loci.shape.sourceData as MeshlistData : null),
map(data => (data?.ownerId === this.id) ? data : null), // do not process shapes created by others
distinctUntilChanged((old, current) => old?.segmentId === current?.segmentId),
);
private focusSubscription?: PluginCommand.Subscription = undefined;
private backgroundSegmentsInitialized = false;
constructor(plugin: PluginContext, data: MeshServerInfo.Data, params: Params.Values) {
super(plugin, params);
this.id = UUID.create22();
this.parentData = data;
}
register(ref: string): void {
this.ref = ref;
}
unregister(): void {
if (this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = undefined;
}
// TODO empty cache here (if used)
}
selectSegment(segmentId: number) {
if (this.params.view.name === 'select') {
if (this.params.view.params.selectedSegment === segmentId) return;
const newParams = Params.copyValues(this.params);
if (newParams.view.name === 'select') {
newParams.view.params.selectedSegment = segmentId;
}
const state = this.plugin.state.data;
const update = state.build().to(this.ref).update(newParams);
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
}
async update(params: Params.Values) {
const oldParams = this.params;
this.params = params;
if (!this.metadata) {
const response = await fetch(this.getMetadataUrl());
this.metadata = await response.json();
}
if (!this.visuals) {
this.initVisualInfos();
} else if (!Params.detailsEqual(this.params, oldParams)) {
this.updateVisualInfoDetails();
}
switch (params.view.name) {
case 'off':
await this.disableVisuals();
break;
case 'select':
await this.enableVisuals(params.view.params.selectedSegment);
break;
case 'all':
await this.enableVisuals();
break;
default:
throw new Error('Not implemented');
}
if (params.view.name !== 'off' && !this.backgroundSegmentsInitialized) {
this.guessBackgroundSegments();
this.backgroundSegmentsInitialized = true;
}
if (params.view.name === 'select' && !this.focusSubscription) {
this.focusSubscription = this.subscribeObservable(this.focusObservable, data => { this.selectSegment(data?.segmentId ?? NO_SEGMENT); });
} else if (params.view.name !== 'select' && this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = undefined;
}
return true;
}
private getMetadataUrl() {
return `${this.parentData.serverUrl}/${this.parentData.source}/${this.parentData.entryId}/metadata`;
}
private getMeshUrl(segment: number, detail: number) {
return `${this.parentData.serverUrl}/${this.parentData.source}/${this.parentData.entryId}/mesh_bcif/${segment}/${detail}`;
}
private initVisualInfos() {
const namesAndColors = Metadata.namesAndColorsBySegment(this.metadata!);
const visuals: { [tag: string]: VisualInfo } = {};
for (const segid of Metadata.meshSegments(this.metadata!)) {
if (DEBUG_IGNORED_SEGMENTS.has(segid)) continue;
const name = namesAndColors[segid]?.name ?? DEFAULT_SEGMENT_NAME;
const color = namesAndColors[segid]?.color ?? DEFAULT_SEGMENT_COLOR;
for (const detailType of VisualInfo.DetailTypes) {
const visual: VisualInfo = {
tag: VisualInfo.tagFor(segid, detailType),
segmentId: segid,
segmentName: name,
detailType: detailType,
detail: -1, // to be set at the end
color: color,
visible: false,
data: undefined,
};
visuals[visual.tag] = visual;
}
}
this.visuals = visuals;
this.updateVisualInfoDetails();
}
private updateVisualInfoDetails() {
let highDetail: number | undefined;
let lowDetail: number | undefined;
switch (this.params.view.name) {
case 'off':
lowDetail = undefined;
highDetail = undefined;
break;
case 'select':
lowDetail = this.params.view.params.baseDetail;
highDetail = this.params.view.params.focusDetail;
break;
case 'all':
lowDetail = this.params.view.params.detail;
highDetail = undefined;
break;
}
for (const tag in this.visuals) {
const visual = this.visuals[tag];
const preferredDetail = (visual.detailType === 'high') ? highDetail : lowDetail;
if (preferredDetail !== undefined) {
visual.detail = Metadata.getSufficientDetail(this.metadata!, visual.segmentId, preferredDetail);
}
}
}
private async enableVisuals(highDetailSegment?: number) {
for (const tag in this.visuals) {
const visual = this.visuals[tag];
const requiredDetailType = visual.segmentId === highDetailSegment ? 'high' : 'low';
if (visual.detailType === requiredDetailType) {
visual.data = await this.getMeshData(visual);
visual.visible = true;
} else {
visual.visible = false;
}
}
}
private async disableVisuals() {
for (const tag in this.visuals) {
const visual = this.visuals[tag];
visual.visible = false;
}
}
/** Fetch data in current `visual.detail`, or return already fetched data (if available in the correct detail). */
private async getMeshData(visual: VisualInfo): Promise<MeshlistData> {
if (visual.data && visual.data.detail === visual.detail) {
// Do not recreate
return visual.data;
}
// TODO cache
const url = this.getMeshUrl(visual.segmentId, visual.detail);
const urlAsset = Asset.getUrlAsset(this.plugin.managers.asset, url);
const asset = await this.plugin.runTask(this.plugin.managers.asset.resolve(urlAsset, 'binary'));
const parsed = await this.plugin.runTask(CIF.parseBinary(asset.data));
if (parsed.isError) {
throw new Error(`Failed parsing CIF file from ${url}`);
}
const meshlistData = await MeshlistData.fromCIF(parsed.result, visual.segmentId, visual.segmentName, visual.detail);
meshlistData.ownerId = this.id;
// const bbox = MeshlistData.bbox(meshlistData);
// const bboxVolume = bbox ? MS.Box3D.volume(bbox) : 0.0;
// console.log(`BBox ${visual.segmentId}: ${Math.round(bboxVolume! / 1e6)} M`, bbox); // DEBUG
return meshlistData;
}
private async guessBackgroundSegments() {
const bboxes: { [segid: number]: Box3D } = {};
for (const tag in this.visuals) {
const visual = this.visuals[tag];
if (visual.detailType === 'low' && visual.data) {
const bbox = MeshlistData.bbox(visual.data);
if (bbox) {
bboxes[visual.segmentId] = bbox;
}
}
}
const totalBbox = MeshlistData.combineBBoxes(Object.values(bboxes));
const totalVolume = totalBbox ? Box3D.volume(totalBbox) : 0.0;
// console.log(`BBox total: ${Math.round(totalVolume! / 1e6)} M`, totalBbox); // DEBUG
const isBgSegment: { [segid: number]: boolean } = {};
for (const segid in bboxes) {
const bbox = bboxes[segid];
const bboxVolume = Box3D.volume(bbox);
isBgSegment[segid] = (bboxVolume > totalVolume * BACKGROUND_SEGMENT_VOLUME_THRESHOLD);
// console.log(`BBox ${segid}: ${Math.round(bboxVolume! / 1e6)} M, ${bboxVolume / totalVolume}`, bbox); // DEBUG
}
this.backgroundSegments = isBgSegment;
}
getDescription() {
return Params.ViewTypeChoice.prettyName(this.params.view.name);
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Choice } from '../choice';
export const DEFAULT_MESH_SERVER = 'http://localhost:9000/v2';
export class MeshServerInfo extends PluginStateObject.Create<MeshServerInfo.Data>({ name: 'Volume Server', typeClass: 'Object' }) { }
export namespace MeshServerInfo {
export const MeshSourceChoice = new Choice({ empiar: 'EMPIAR', emdb: 'EMDB' }, 'empiar');
export type MeshSource = Choice.Values<typeof MeshSourceChoice>;
export const Params = {
serverUrl: PD.Text(DEFAULT_MESH_SERVER),
source: MeshSourceChoice.PDSelect(),
entryId: PD.Text(''),
};
export type Data = PD.Values<typeof Params>;
}

View File

@@ -0,0 +1,218 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { PluginContext } from '../../../mol-plugin/context';
import { ShapeRepresentation } from '../../../mol-repr/shape/representation';
import { StateAction, StateTransformer } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { shallowEqualObjects } from '../../../mol-util';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { VolsegTransform, MeshlistData } from '../mesh-extension';
import { MeshStreaming, NO_SEGMENT } from './behavior';
import { MeshServerInfo } from './server-info';
export const BACKGROUND_OPACITY = 0.2;
export const FOREROUND_OPACITY = 1;
// // // // // // // // // // // // // // // // // // // // // // // //
export const MeshServerTransformer = VolsegTransform({
name: 'mesh-server-info',
from: PluginStateObject.Root,
to: MeshServerInfo,
params: MeshServerInfo.Params,
})({
apply({ a, params }, plugin: PluginContext) { // `a` is the parent node, `params` are 2nd argument to To.apply()
params.serverUrl = params.serverUrl.replace(/\/*$/, ''); // trim trailing slash
const description: string = params.entryId;
return new MeshServerInfo({ ...params }, { label: 'Mesh Server', description: description });
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
export const MeshStreamingTransformer = VolsegTransform({
name: 'mesh-streaming-from-server-info',
display: { name: 'Mesh Streaming' },
from: MeshServerInfo,
to: MeshStreaming,
params: a => MeshStreaming.Params.create(a!.data),
})({
canAutoUpdate() { return true; },
apply({ a, params }, plugin: PluginContext) {
return Task.create('Mesh Streaming', async ctx => {
const behavior = new MeshStreaming.Behavior(plugin, a.data, params);
await behavior.update(params);
return new MeshStreaming(behavior, { label: 'Mesh Streaming', description: behavior.getDescription() });
});
},
update({ a, b, oldParams, newParams }) {
return Task.create('Update Mesh Streaming', async ctx => {
if (a.data.source !== b.data.parentData.source || a.data.entryId !== b.data.parentData.entryId) {
return StateTransformer.UpdateResult.Recreate;
}
b.data.parentData = a.data;
await b.data.update(newParams);
b.description = b.data.getDescription();
return StateTransformer.UpdateResult.Updated;
});
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
interface MeshVisualGroupData {
opacity: number,
}
// export type MeshVisualGroupTransformer = typeof MeshVisualGroupTransformer;
export const MeshVisualGroupTransformer = VolsegTransform({
name: 'mesh-visual-group-from-streaming',
display: { name: 'Mesh Visuals for a Segment' },
from: MeshStreaming,
to: PluginStateObject.Group,
params: {
/** Shown on the node in GUI */
label: PD.Text('', { isHidden: true }),
/** Shown on the node in GUI (gray letters) */
description: PD.Text(''),
segmentId: PD.Numeric(NO_SEGMENT, {}, { isHidden: true }),
opacity: PD.Numeric(-1, { min: 0, max: 1, step: 0.01 }),
}
})({
apply({ a, params }, plugin) {
trySetAutoOpacity(params, a);
return new PluginStateObject.Group({ opacity: params.opacity }, params);
},
update({ a, b, oldParams, newParams }, plugin) {
if (shallowEqualObjects(oldParams, newParams)) {
return StateTransformer.UpdateResult.Unchanged;
}
newParams.label ||= oldParams.label; // Protect against resetting params to invalid defaults
if (newParams.segmentId === NO_SEGMENT) newParams.segmentId = oldParams.segmentId; // Protect against resetting params to invalid defaults
trySetAutoOpacity(newParams, a);
b.label = newParams.label;
b.description = newParams.description;
(b.data as MeshVisualGroupData).opacity = newParams.opacity;
return StateTransformer.UpdateResult.Updated;
},
canAutoUpdate({ oldParams, newParams }, plugin) {
return newParams.description === oldParams.description;
},
});
function trySetAutoOpacity(params: StateTransformer.Params<typeof MeshVisualGroupTransformer>, parent: MeshStreaming) {
if (params.opacity === -1) {
const isBgSegment = parent.data.backgroundSegments[params.segmentId];
if (isBgSegment !== undefined) {
params.opacity = isBgSegment ? BACKGROUND_OPACITY : FOREROUND_OPACITY;
}
}
}
// // // // // // // // // // // // // // // // // // // // // // // //
export const MeshVisualTransformer = VolsegTransform({
name: 'mesh-visual-from-streaming',
display: { name: 'Mesh Visual from Streaming' },
from: MeshStreaming,
to: PluginStateObject.Shape.Representation3D,
params: {
/** Must be set to PluginStateObject reference to self */
ref: PD.Text('', { isHidden: true, isEssential: true }), // QUESTION what is isEssential
/** Identification of the mesh visual, e.g. 'low-2' */
tag: PD.Text('', { isHidden: true, isEssential: true }),
/** Opacity of the visual (not to be set directly, but controlled by the opacity of the parent Group, and by VisualInfo.visible) */
opacity: PD.Numeric(-1, { min: 0, max: 1, step: 0.01 }, { isHidden: true }),
}
})({
apply({ a, params, spine }, plugin: PluginContext) {
return Task.create('Mesh Visual', async ctx => {
const visualInfo: MeshStreaming.VisualInfo = a.data.visuals![params.tag];
if (!visualInfo) throw new Error(`VisualInfo with tag '${params.tag}' is missing.`);
const groupData = spine.getAncestorOfType(PluginStateObject.Group)?.data as MeshVisualGroupData | undefined;
params.opacity = visualInfo.visible ? (groupData?.opacity ?? FOREROUND_OPACITY) : 0.0;
const props = PD.getDefaultValues(Mesh.Params);
props.flatShaded = true; // `flatShaded: true` is to see the real mesh vertices and triangles (default: false)
props.alpha = params.opacity;
const repr = ShapeRepresentation((ctx, meshlist: MeshlistData) => MeshlistData.getShape(meshlist, visualInfo.color), Mesh.Utils);
await repr.createOrUpdate(props, visualInfo.data ?? MeshlistData.empty()).runInContext(ctx);
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: visualInfo.data }, { label: 'Mesh Visual', description: params.tag });
});
},
update({ a, b, oldParams, newParams, spine }, plugin: PluginContext) {
return Task.create('Update Mesh Visual', async ctx => {
newParams.ref ||= oldParams.ref; // Protect against resetting params to invalid defaults
newParams.tag ||= oldParams.tag; // Protect against resetting params to invalid defaults
const visualInfo: MeshStreaming.VisualInfo = a.data.visuals![newParams.tag];
if (!visualInfo) throw new Error(`VisualInfo with tag '${newParams.tag}' is missing.`);
const oldData = b.data.sourceData as MeshlistData | undefined;
if (visualInfo.data?.detail !== oldData?.detail) {
return StateTransformer.UpdateResult.Recreate;
}
const groupData = spine.getAncestorOfType(PluginStateObject.Group)?.data as MeshVisualGroupData | undefined;
const newOpacity = visualInfo.visible ? (groupData?.opacity ?? FOREROUND_OPACITY) : 0.0; // do not store to newParams directly, because oldParams and newParams might point to the same object!
if (newOpacity !== oldParams.opacity) {
newParams.opacity = newOpacity;
await b.data.repr.createOrUpdate({ alpha: newParams.opacity }).runInContext(ctx);
return StateTransformer.UpdateResult.Updated;
} else {
return StateTransformer.UpdateResult.Unchanged;
}
});
},
canAutoUpdate(params, globalCtx) {
return true;
},
dispose({ b, params }, plugin) {
b?.data.repr.destroy(); // QUESTION is this correct?
},
});
// // // // // // // // // // // // // // // // // // // // // // // //
export const InitMeshStreaming = StateAction.build({
display: { name: 'Mesh Streaming' },
from: PluginStateObject.Root,
params: MeshServerInfo.Params,
isApplicable: (a, _, plugin: PluginContext) => true
})(function (p, plugin: PluginContext) {
return Task.create('Mesh Streaming', async ctx => {
const { params } = p;
// p.ref
const serverNode = await plugin.build().to(p.ref).apply(MeshServerTransformer, params).commit();
// const serverNode = await plugin.build().toRoot().apply(MeshServerTransformer, params).commit();
const streamingNode = await plugin.build().to(serverNode).apply(MeshStreamingTransformer, {}).commit();
const visuals = streamingNode.data?.visuals ?? {};
const bgSegments = streamingNode.data?.backgroundSegments ?? {};
const segmentGroups: { [segid: number]: string } = {};
for (const tag in visuals) {
const segid = visuals[tag].segmentId;
if (!segmentGroups[segid]) {
let description = visuals[tag].segmentName;
if (bgSegments[segid]) description += ' (background)';
const group = await plugin.build().to(streamingNode).apply(MeshVisualGroupTransformer, { label: `Segment ${segid}`, description: description, segmentId: segid }, { state: { isCollapsed: true } }).commit();
segmentGroups[segid] = group.ref;
}
}
const visualsUpdate = plugin.build();
for (const tag in visuals) {
const ref = `${streamingNode.ref}-${tag}`;
const segid = visuals[tag].segmentId;
visualsUpdate.to(segmentGroups[segid]).apply(MeshVisualTransformer, { ref: ref, tag: tag }, { ref: ref }); // ref - hack to allow the node make itself invisible
}
await plugin.state.data.updateTree(visualsUpdate).runInContext(ctx); // QUESTION what is really the difference between this and `visualsUpdate.commit()`?
});
});
// TODO make available in GUI, in left panel or in right panel like Volume Streaming in src/mol-plugin-ui/structure/volume.tsx?

View File

@@ -0,0 +1,340 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
/** Helper functions for manipulation with mesh data. */
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { CIF, CifFile } from '../../mol-io/reader/cif';
import { Box3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { volumeFromDensityServerData } from '../../mol-model-formats/volume/density-server';
import { Grid } from '../../mol-model/volume';
import { ColorNames } from '../../mol-util/color/names';
import { TypedArray } from '../../mol-util/type-helpers';
import { CIF_schema_mesh } from './mesh-cif-schema';
type MeshModificationParams = {
scale?: [number, number, number],
shift?: [number, number, number],
matrix?: Mat4,
group?: number,
invertSides?: boolean
};
/** Modify mesh in-place */
export function modify(m: Mesh, params: MeshModificationParams) {
if (params.scale !== undefined) {
const [qx, qy, qz] = params.scale;
const vertices = m.vertexBuffer.ref.value;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i] *= qx;
vertices[i + 1] *= qy;
vertices[i + 2] *= qz;
}
}
if (params.shift !== undefined) {
const [dx, dy, dz] = params.shift;
const vertices = m.vertexBuffer.ref.value;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i] += dx;
vertices[i + 1] += dy;
vertices[i + 2] += dz;
}
}
if (params.matrix !== undefined) {
const r = m.vertexBuffer.ref.value;
const matrix = params.matrix;
const size = 3 * m.vertexCount;
for (let i = 0; i < size; i += 3) {
Vec3.transformMat4Offset(r, r, matrix, i, i, 0);
}
}
if (params.group !== undefined) {
const groups = m.groupBuffer.ref.value;
for (let i = 0; i < groups.length; i++) {
groups[i] = params.group;
}
}
if (params.invertSides) {
const indices = m.indexBuffer.ref.value;
let tmp;
for (let i = 0; i < indices.length; i += 3) {
tmp = indices[i];
indices[i] = indices[i + 1];
indices[i + 1] = tmp;
}
const normals = m.normalBuffer.ref.value;
for (let i = 0; i < normals.length; i++) {
normals[i] *= -1;
}
}
}
/** Create a copy a mesh, possibly modified */
export function copy(m: Mesh, modification?: MeshModificationParams): Mesh {
const nVertices = m.vertexCount;
const nTriangles = m.triangleCount;
const vertices = new Float32Array(m.vertexBuffer.ref.value);
const indices = new Uint32Array(m.indexBuffer.ref.value);
const normals = new Float32Array(m.normalBuffer.ref.value);
const groups = new Float32Array(m.groupBuffer.ref.value);
const result = Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
if (modification) {
modify(result, modification);
}
return result;
}
/** Join more meshes into one */
export function concat(...meshes: Mesh[]): Mesh {
const nVertices = sum(meshes.map(m => m.vertexCount));
const nTriangles = sum(meshes.map(m => m.triangleCount));
const vertices = concatArrays(Float32Array, meshes.map(m => m.vertexBuffer.ref.value));
const normals = concatArrays(Float32Array, meshes.map(m => m.normalBuffer.ref.value));
const groups = concatArrays(Float32Array, meshes.map(m => m.groupBuffer.ref.value));
const newIndices = [];
let offset = 0;
for (const m of meshes) {
newIndices.push(m.indexBuffer.ref.value.map(i => i + offset));
offset += m.vertexCount;
}
const indices = concatArrays(Uint32Array, newIndices);
return Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
}
/** Return Mesh from CIF data and mesh IDs (group IDs).
* Assume the CIF contains coords in grid space,
* transform the output mesh to `space` */
export async function meshFromCif(data: CifFile, invertSides: boolean | undefined = undefined, outSpace: 'grid' | 'fractional' | 'cartesian' = 'cartesian'): Promise<{ mesh: Mesh, meshIds: number[] }> {
const volumeInfoBlock = data.blocks.find(b => b.header === 'VOLUME_INFO');
const meshesBlock = data.blocks.find(b => b.header === 'MESHES');
if (!volumeInfoBlock || !meshesBlock) throw new Error('Missing VOLUME_INFO or MESHES block in mesh CIF file');
const volumeInfoCif = CIF.schema.densityServer(volumeInfoBlock);
const meshCif = CIF_schema_mesh(meshesBlock);
const nVertices = meshCif.mesh_vertex._rowCount;
const nTriangles = Math.floor(meshCif.mesh_triangle._rowCount / 3);
const mesh_id = meshCif.mesh.id.toArray();
const vertex_meshId = meshCif.mesh_vertex.mesh_id.toArray();
const x = meshCif.mesh_vertex.x.toArray();
const y = meshCif.mesh_vertex.y.toArray();
const z = meshCif.mesh_vertex.z.toArray();
const triangle_meshId = meshCif.mesh_triangle.mesh_id.toArray();
const triangle_vertexId = meshCif.mesh_triangle.vertex_id.toArray();
// Shift indices from within-mesh indices to overall indices
const indices = new Uint32Array(3 * nTriangles);
const offsets = offsetMap(vertex_meshId);
for (let i = 0; i < 3 * nTriangles; i++) {
const offset = offsets.get(triangle_meshId[i])!;
indices[i] = offset + triangle_vertexId[i];
}
const vertices = flattenCoords(x, y, z);
const normals = new Float32Array(3 * nVertices);
const groups = new Float32Array(vertex_meshId);
const mesh = Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
invertSides ??= isInverted(mesh);
if (invertSides) {
modify(mesh, { invertSides: true }); // Vertex orientation convention is opposite in Volseg API and in MolStar
}
if (outSpace === 'cartesian') {
const volume = await volumeFromDensityServerData(volumeInfoCif).run();
const gridToCartesian = Grid.getGridToCartesianTransform(volume.grid);
modify(mesh, { matrix: gridToCartesian });
} else if (outSpace === 'fractional') {
const gridSize = volumeInfoCif.volume_data_3d_info.sample_count.value(0);
const originFract = volumeInfoCif.volume_data_3d_info.origin.value(0);
const dimensionFract = volumeInfoCif.volume_data_3d_info.dimensions.value(0);
if (dimensionFract[0] !== 1 || dimensionFract[1] !== 1 || dimensionFract[2] !== 1) throw new Error(`Asserted the fractional dimensions are [1,1,1], but are actually [${dimensionFract}]`);
const scale: [number, number, number] = [1 / gridSize[0], 1 / gridSize[1], 1 / gridSize[2]];
modify(mesh, { scale: scale, shift: Array.from(originFract) as any });
}
Mesh.computeNormals(mesh); // normals only necessary if flatShaded==false
// const boxMesh = makeMeshFromBox([[0,0,0], [1,1,1]], 1);
// const gridSize = volumeInfoCif.volume_data_3d_info.sample_count.value(0); const boxMesh = makeMeshFromBox([[0,0,0], Array.from(gridSize)] as any, 1);
// const cellSize = volumeInfoCif.volume_data_3d_info.spacegroup_cell_size.value(0); const boxMesh = makeMeshFromBox([[0, 0, 0], Array.from(cellSize)] as any, 1);
// mesh = concat(mesh, boxMesh); // debug
return { mesh: mesh, meshIds: Array.from(mesh_id) };
}
function isInverted(mesh: Mesh): boolean {
const vertices = mesh.vertexBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const center = meshCenter(mesh);
const center3 = Vec3.create(3 * center[0], 3 * center[1], 3 * center[2]);
let dirMetric = 0.0;
const [a, b, c, u, v, normal, radius] = [Vec3(), Vec3(), Vec3(), Vec3(), Vec3(), Vec3(), Vec3()];
for (let i = 0; i < indices.length; i += 3) {
Vec3.fromArray(a, vertices, 3 * indices[i]);
Vec3.fromArray(b, vertices, 3 * indices[i + 1]);
Vec3.fromArray(c, vertices, 3 * indices[i + 2]);
Vec3.sub(u, b, a);
Vec3.sub(v, c, b);
Vec3.cross(normal, u, v); // direction of the surface
Vec3.add(radius, a, b);
Vec3.add(radius, radius, c);
Vec3.sub(radius, radius, center3); // direction center -> this triangle
dirMetric += Vec3.dot(radius, normal);
}
return dirMetric < 0;
}
function meshCenter(mesh: Mesh) {
const vertices = mesh.vertexBuffer.ref.value;
const n = vertices.length;
let x = 0.0;
let y = 0.0;
let z = 0.0;
for (let i = 0; i < vertices.length; i += 3) {
x += vertices[i];
y += vertices[i + 1];
z += vertices[i + 2];
}
return Vec3.create(x / n, y / n, z / n);
}
function flattenCoords(x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number>): Float32Array {
const n = x.length;
const out = new Float32Array(3 * n);
for (let i = 0; i < n; i++) {
out[3 * i] = x[i];
out[3 * i + 1] = y[i];
out[3 * i + 2] = z[i];
}
return out;
}
/** Get mapping of unique values to the position of their first occurrence */
function offsetMap(values: ArrayLike<number>) {
const result = new Map<number, number>();
for (let i = 0; i < values.length; i++) {
if (!result.has(values[i])) {
result.set(values[i], i);
}
}
return result;
}
/** Return bounding box */
export function bbox(mesh: Mesh): Box3D | null { // Is there no function for this?
const nVertices = mesh.vertexCount;
const coords = mesh.vertexBuffer.ref.value;
if (nVertices === 0) {
return null;
}
let minX = coords[0], minY = coords[1], minZ = coords[2];
let maxX = minX, maxY = minY, maxZ = minZ;
for (let i = 0; i < 3 * nVertices; i += 3) {
const x = coords[i], y = coords[i + 1], z = coords[i + 2];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (z < minZ) minZ = z;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
if (z > maxZ) maxZ = z;
}
return Box3D.create(Vec3.create(minX, minY, minZ), Vec3.create(maxX, maxY, maxZ));
}
/** Example mesh - 1 triangle */
export function fakeFakeMesh1(): Mesh {
const nVertices = 3;
const nTriangles = 1;
const vertices = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
const indices = new Uint32Array([0, 1, 2]);
const normals = new Float32Array([0, 0, 1]);
const groups = new Float32Array([0]);
return Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
}
/** Example mesh - irregular tetrahedron */
export function fakeMesh4(): Mesh {
const nVertices = 4;
const nTriangles = 4;
const vertices = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]);
const indices = new Uint32Array([0, 2, 1, 0, 1, 3, 1, 2, 3, 2, 0, 3]);
const normals = new Float32Array([-1, -1, -1, 1, 0, 0, 0, 1, 0, 0, 0, 1]);
const groups = new Float32Array([0, 1, 2, 3]);
return Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
}
/** Return a box-shaped mesh */
export function meshFromBox(box: [[number, number, number], [number, number, number]], group: number = 0) {
const [[x0, y0, z0], [x1, y1, z1]] = box;
const vertices = new Float32Array([
x0, y0, z0,
x1, y0, z0,
x0, y1, z0,
x1, y1, z0,
x0, y0, z1,
x1, y0, z1,
x0, y1, z1,
x1, y1, z1,
]);
const indices = new Uint32Array([
2, 1, 0, 1, 2, 3,
1, 4, 0, 4, 1, 5,
3, 5, 1, 5, 3, 7,
2, 7, 3, 7, 2, 6,
0, 6, 2, 6, 0, 4,
4, 7, 6, 7, 4, 5,
]);
const groups = new Float32Array([group, group, group, group, group, group, group, group]);
const normals = new Float32Array(8);
const mesh = Mesh.create(vertices, indices, normals, groups, 8, 12);
Mesh.computeNormals(mesh); // normals only necessary if flatShaded==false
return mesh;
}
function sum(array: number[]): number {
return array.reduce((a, b) => a + b, 0);
}
function concatArrays<T extends TypedArray>(t: new (len: number) => T, arrays: T[]): T {
const totalLength = arrays.map(a => a.length).reduce((a, b) => a + b, 0);
const result: T = new t(totalLength);
let offset = 0;
for (const array of arrays) {
result.set(array, offset);
offset += array.length;
}
return result;
}
/** Generate random colors (in a cycle) */
export const ColorGenerator = function* () {
const colors = shuffleArray(Object.values(ColorNames));
let i = 0;
while (true) {
yield colors[i];
i++;
if (i >= colors.length) i = 0;
}
}();
function shuffleArray<T>(array: T[]): T[] {
// Stealed from https://www.w3docs.com/snippets/javascript/how-to-randomize-shuffle-a-javascript-array.html
let curId = array.length;
// There remain elements to shuffle
while (0 !== curId) {
// Pick a remaining element
const randId = Math.floor(Math.random() * curId);
curId -= 1;
// Swap it with the current element.
const tmp = array[curId];
array[curId] = array[randId];
array[randId] = tmp;
}
return array;
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Color } from '../../mol-util/color';
// TODO unify with Metadata in Volseg
export interface Metadata {
grid: {
general: {
details: string,
},
volumes: Volumes,
segmentation_lattices: SegmentationLattices,
segmentation_meshes: SegmentationMeshes,
},
annotation: Annotation,
}
export interface Volumes {
volume_downsamplings: number[],
voxel_size: { [downsampling: number]: Vector3 },
origin: Vector3,
grid_dimensions: Vector3,
sampled_grid_dimensions: { [downsampling: number]: Vector3 },
mean: { [downsampling: number]: number },
std: { [downsampling: number]: number },
min: { [downsampling: number]: number },
max: { [downsampling: number]: number },
volume_force_dtype: string,
}
export interface SegmentationLattices {
segmentation_lattice_ids: number[],
segmentation_downsamplings: { [lattice: number]: number[] },
}
export interface Annotation {
name: string,
details: string,
segment_list: Segment[],
}
export interface Segment {
id: number,
colour: number[],
biological_annotation: BiologicalAnnotation,
}
export interface BiologicalAnnotation {
name: string,
external_references: { id: number, resource: string, accession: string, label: string, description: string }[]
}
export interface SegmentationMeshes {
mesh_component_numbers: {
segment_ids?: {
[segId: number]: {
detail_lvls: {
[detail: number]: {
mesh_ids: {
[meshId: number]: {
num_triangles: number,
num_vertices: number
}
}
}
}
}
}
}
detail_lvl_to_fraction: {
[lvl: number]: number
}
}
type Vector3 = [number, number, number];
export namespace Metadata {
export function meshSegments(metadata: Metadata): number[] {
const segmentIds = metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids;
if (segmentIds === undefined) return [];
return Object.keys(segmentIds).map(s => parseInt(s));
}
export function meshSegmentDetails(metadata: Metadata, segmentId: number): number[] {
const segmentIds = metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids;
if (segmentIds === undefined) return [];
const details = segmentIds[segmentId].detail_lvls;
return Object.keys(details).map(s => parseInt(s));
}
/** Get the worst available detail level that is not worse than preferredDetail.
* If preferredDetail is null, get the worst detail level overall.
* (worse = greater number) */
export function getSufficientDetail(metadata: Metadata, segmentId: number, preferredDetail: number | null) {
let availDetails = meshSegmentDetails(metadata, segmentId);
if (preferredDetail !== null) {
availDetails = availDetails.filter(det => det <= preferredDetail);
}
return Math.max(...availDetails);
}
export function annotationsBySegment(metadata: Metadata): { [id: number]: Segment } {
const result: { [id: number]: Segment } = {};
for (const segment of metadata.annotation.segment_list) {
if (segment.id in result) {
throw new Error(`Duplicate segment annotation for segment ${segment.id}`);
}
result[segment.id] = segment;
}
return result;
}
export function dropSegments(metadata: Metadata, segments: number[]): void {
if (metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids === undefined) return;
const dropSet = new Set(segments);
metadata.annotation.segment_list = metadata.annotation.segment_list.filter(seg => !dropSet.has(seg.id));
for (const seg of segments) {
delete metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids[seg];
}
}
export function namesAndColorsBySegment(metadata: Metadata) {
const result: { [id: number]: { name: string, color: Color } } = {};
for (const segment of metadata.annotation.segment_list) {
if (segment.id in result) throw new Error(`Duplicate segment annotation for segment ${segment.id}`);
result[segment.id] = { name: segment.biological_annotation.name, color: Color.fromNormalizedArray(segment.colour, 0) };
}
return result;
}
}

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -14,6 +15,7 @@ import { CustomPropSymbol } from '../../../mol-script/language/symbol';
import { Type } from '../../../mol-script/language/type';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { AtomicIndex } from '../../../mol-model/structure/model/properties/atomic';
export { QualityAssessment };
@@ -71,14 +73,28 @@ namespace QualityAssessment {
localNames.set(ma_qa_metric.id.value(i), name);
}
const residueKey: AtomicIndex.ResidueLabelKey = {
label_entity_id: '',
label_asym_id: '',
label_seq_id: 0,
pdbx_PDB_ins_code: undefined,
};
for (let i = 0, il = ma_qa_metric_local._rowCount; i < il; i++) {
if (model_id.value(i) !== model.modelNum) continue;
const labelAsymId = label_asym_id.value(i);
const entityIndex = index.findEntity(labelAsymId);
const rI = index.findResidue(model.entities.data.id.value(entityIndex), labelAsymId, label_seq_id.value(i));
const name = localNames.get(metric_id.value(i))!;
localMetrics.get(name)!.set(rI, metric_value.value(i));
residueKey.label_entity_id = model.entities.data.id.value(entityIndex);
residueKey.label_asym_id = labelAsymId;
residueKey.label_seq_id = label_seq_id.value(i);
const rI = index.findResidueLabel(residueKey);
if (rI >= 0) {
const name = localNames.get(metric_id.value(i))!;
localMetrics.get(name)!.set(rI, metric_value.value(i));
}
}
return {

View File

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

View File

@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 2022-08-20T16:36:05-07:00
// Generated on 2022-12-03T21:55:37-08:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -343,6 +343,14 @@ export type Citation = {
*
*/
readonly journal_abbrev?: Maybe<Scalars['String']>;
/**
* Full name of the cited journal; relevant for journal articles.
*
* Examples:
* Journal of Molecular Biology
*
*/
readonly journal_full?: Maybe<Scalars['String']>;
/**
* The American Society for Testing and Materials (ASTM) code
* assigned to the journal cited (also referred to as the CODEN
@@ -550,6 +558,7 @@ export type CoreBranchedEntityInstance = {
readonly rcsb_id: Scalars['String'];
readonly rcsb_latest_revision?: Maybe<RcsbLatestRevision>;
readonly rcsb_ligand_neighbors?: Maybe<ReadonlyArray<Maybe<RcsbLigandNeighbors>>>;
readonly struct_asym?: Maybe<StructAsym>;
};
export type CoreChemComp = {
@@ -661,6 +670,7 @@ export type CoreEntry = {
readonly exptl?: Maybe<ReadonlyArray<Maybe<Exptl>>>;
readonly exptl_crystal?: Maybe<ReadonlyArray<Maybe<ExptlCrystal>>>;
readonly exptl_crystal_grow?: Maybe<ReadonlyArray<Maybe<ExptlCrystalGrow>>>;
readonly ma_data?: Maybe<ReadonlyArray<Maybe<MaData>>>;
/** Get all non-polymer (non-solvent) entities for this PDB entry. */
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
readonly pdbx_SG_project?: Maybe<ReadonlyArray<Maybe<PdbxSgProject>>>;
@@ -701,6 +711,7 @@ export type CoreEntry = {
/** The list of content types associated with this entry. */
readonly rcsb_associated_holdings?: Maybe<CurrentEntry>;
readonly rcsb_binding_affinity?: Maybe<ReadonlyArray<Maybe<RcsbBindingAffinity>>>;
readonly rcsb_comp_model_provenance?: Maybe<RcsbCompModelProvenance>;
readonly rcsb_entry_container_identifiers: RcsbEntryContainerIdentifiers;
readonly rcsb_entry_group_membership?: Maybe<ReadonlyArray<Maybe<RcsbEntryGroupMembership>>>;
readonly rcsb_entry_info: RcsbEntryInfo;
@@ -713,6 +724,7 @@ export type CoreEntry = {
*
*/
readonly rcsb_id: Scalars['String'];
readonly rcsb_ma_qa_metric_global?: Maybe<ReadonlyArray<Maybe<RcsbMaQaMetricGlobal>>>;
readonly rcsb_primary_citation?: Maybe<RcsbPrimaryCitation>;
readonly refine?: Maybe<ReadonlyArray<Maybe<Refine>>>;
readonly refine_analyze?: Maybe<ReadonlyArray<Maybe<RefineAnalyze>>>;
@@ -786,6 +798,7 @@ export type CoreNonpolymerEntityInstance = {
readonly rcsb_nonpolymer_instance_validation_score?: Maybe<ReadonlyArray<Maybe<RcsbNonpolymerInstanceValidationScore>>>;
readonly rcsb_nonpolymer_struct_conn?: Maybe<ReadonlyArray<Maybe<RcsbNonpolymerStructConn>>>;
readonly rcsb_target_neighbors?: Maybe<ReadonlyArray<Maybe<RcsbTargetNeighbors>>>;
readonly struct_asym?: Maybe<StructAsym>;
};
export type CorePfam = {
@@ -919,6 +932,7 @@ export type CorePolymerEntityInstance = {
readonly rcsb_polymer_instance_feature?: Maybe<ReadonlyArray<Maybe<RcsbPolymerInstanceFeature>>>;
readonly rcsb_polymer_instance_feature_summary?: Maybe<ReadonlyArray<Maybe<RcsbPolymerInstanceFeatureSummary>>>;
readonly rcsb_polymer_struct_conn?: Maybe<ReadonlyArray<Maybe<RcsbPolymerStructConn>>>;
readonly struct_asym?: Maybe<StructAsym>;
};
export type CorePubmed = {
@@ -1284,9 +1298,9 @@ export type Em2dCrystalEntity = {
readonly id: Scalars['String'];
/** pointer to _em_image_processing.id in the EM_IMAGE_PROCESSING category. */
readonly image_processing_id: Scalars['String'];
/** Unit-cell length a in Angstroms. */
/** Unit-cell length a in angstroms. */
readonly length_a?: Maybe<Scalars['Float']>;
/** Unit-cell length b in Angstroms. */
/** Unit-cell length b in angstroms. */
readonly length_b?: Maybe<Scalars['Float']>;
/** Thickness of 2D crystal */
readonly length_c?: Maybe<Scalars['Float']>;
@@ -1317,11 +1331,11 @@ export type Em3dCrystalEntity = {
readonly id: Scalars['String'];
/** pointer to _em_image_processing.id in the EM_IMAGE_PROCESSING category. */
readonly image_processing_id: Scalars['String'];
/** Unit-cell length a in Angstroms. */
/** Unit-cell length a in angstroms. */
readonly length_a?: Maybe<Scalars['Float']>;
/** Unit-cell length b in Angstroms. */
/** Unit-cell length b in angstroms. */
readonly length_b?: Maybe<Scalars['Float']>;
/** Unit-cell length c in Angstroms. */
/** Unit-cell length c in angstroms. */
readonly length_c?: Maybe<Scalars['Float']>;
/**
* Space group name.
@@ -1482,7 +1496,7 @@ export type Em3dReconstruction = {
*/
readonly refinement_type?: Maybe<Scalars['String']>;
/**
* The final resolution (in Angstroms)of the 3D reconstruction.
* The final resolution (in angstroms)of the 3D reconstruction.
*
* Examples:
* null, null
@@ -1558,7 +1572,7 @@ export type EmDiffractionShell = {
*/
readonly fourier_space_coverage?: Maybe<Scalars['Float']>;
/**
* High resolution limit for this shell (Angstroms)
* High resolution limit for this shell (angstroms)
*
* Examples:
* null
@@ -1568,7 +1582,7 @@ export type EmDiffractionShell = {
/** Unique identifier for the category em_diffraction_shell */
readonly id: Scalars['String'];
/**
* Low resolution limit for this shell (Angstroms)
* Low resolution limit for this shell (angstroms)
*
* Examples:
* null
@@ -1614,7 +1628,7 @@ export type EmDiffractionStats = {
*/
readonly fourier_space_coverage?: Maybe<Scalars['Float']>;
/**
* High resolution limit of the structure factor data, in Angstroms
* High resolution limit of the structure factor data, in angstroms
*
* Examples:
* null
@@ -1998,12 +2012,12 @@ export type EmImaging = {
/** The magnification indicated by the microscope readout. */
readonly nominal_magnification?: Maybe<Scalars['Int']>;
/**
* The specimen temperature maximum (degrees Kelvin) for the duration
* The specimen temperature maximum (kelvin) for the duration
* of imaging.
*/
readonly recording_temperature_maximum?: Maybe<Scalars['Float']>;
/**
* The specimen temperature minimum (degrees Kelvin) for the duration
* The specimen temperature minimum (kelvin) for the duration
* of imaging.
*/
readonly recording_temperature_minimum?: Maybe<Scalars['Float']>;
@@ -2028,7 +2042,7 @@ export type EmImaging = {
/** Foreign key to the EM_SPECIMEN category */
readonly specimen_id?: Maybe<Scalars['String']>;
/**
* The mean specimen stage temperature (degrees Kelvin) during imaging
* The mean specimen stage temperature (in kelvin) during imaging
* in the microscope.
*/
readonly temperature?: Maybe<Scalars['Float']>;
@@ -2215,7 +2229,7 @@ export type EmStaining = {
};
export type EmVitrification = {
/** The temperature (in degrees Kelvin) of the sample just prior to vitrification. */
/** The temperature (in kelvin) of the sample just prior to vitrification. */
readonly chamber_temperature?: Maybe<Scalars['Float']>;
/**
* This is the name of the cryogen.
@@ -2259,7 +2273,7 @@ export type EmVitrification = {
/** This data item is a pointer to _em_specimen.id */
readonly specimen_id: Scalars['String'];
/**
* The vitrification temperature (in degrees Kelvin), e.g.,
* The vitrification temperature (in kelvin), e.g.,
* temperature of the plunge instrument cryogen bath.
*/
readonly temp?: Maybe<Scalars['Float']>;
@@ -2364,6 +2378,14 @@ export type EntityPoly = {
*
*/
readonly pdbx_seq_one_letter_code_can?: Maybe<Scalars['String']>;
/**
* Evidence for the assignment of the polymer sequence.
*
* Allowable values:
* depositor provided, derived from coordinates
*
*/
readonly pdbx_sequence_evidence_code?: Maybe<Scalars['String']>;
/**
* The PDB strand/chain id(s) corresponding to this polymer entity.
*
@@ -3005,6 +3027,8 @@ export type Entry = {
* identifier.
*/
readonly id: Scalars['String'];
/** An identifier for the model collection associated with the entry. */
readonly ma_collection_id?: Maybe<Scalars['String']>;
};
export type Exptl = {
@@ -3263,6 +3287,29 @@ export type InterfacePartnerFeatureFeaturePositions = {
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
};
export type MaData = {
/**
* The type of data held in the dataset.
*
* Allowable values:
* coevolution MSA, input structure, model coordinates, other, polymeric template library, spatial restraints, target, target-template alignment, template structure
*
*/
readonly content_type?: Maybe<Scalars['String']>;
/** Details for other content types. */
readonly content_type_other_details?: Maybe<Scalars['String']>;
/** A unique identifier for the data. */
readonly id: Scalars['Int'];
/**
* An author-given name for the content held in the dataset.
*
* Examples:
* NMR NOE Distances, Target Template Alignment, Coevolution Data
*
*/
readonly name?: Maybe<Scalars['String']>;
};
export type MethodDetails = {
/** A description of special aspects of the clustering process */
readonly description?: Maybe<Scalars['String']>;
@@ -4199,7 +4246,7 @@ export type PdbxNmrExptlSampleConditions = {
*/
readonly pressure_units?: Maybe<Scalars['String']>;
/**
* The temperature (in Kelvin) at which NMR data were
* The temperature (in kelvin) at which NMR data were
* collected.
*/
readonly temperature?: Maybe<Scalars['String']>;
@@ -4453,7 +4500,7 @@ export type PdbxPrdAudit = {
* An identifier for the wwPDB site creating or modifying the molecule.
*
* Allowable values:
* BMRB, PDBC, PDBJ, PDBe, RCSB
* BMRB, PDBC, PDBE, PDBJ, RCSB
*
*/
readonly processing_site?: Maybe<Scalars['String']>;
@@ -6817,7 +6864,7 @@ export type Query = {
readonly assemblies?: Maybe<ReadonlyArray<Maybe<CoreAssembly>>>;
/** Get an assembly given the PDB ID and ASSEMBLY ID. Here ASSEMBLY ID is '1', '2', '3', etc. or 'deposited' for deposited coordinates. */
readonly assembly?: Maybe<CoreAssembly>;
/** Get a list of PDB branched entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
/** Get a list of PDB branched entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. Note that the ENTRY ID part must be upper case. */
readonly branched_entities?: Maybe<ReadonlyArray<Maybe<CoreBranchedEntity>>>;
/** Get a PDB branched entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
readonly branched_entity?: Maybe<CoreBranchedEntity>;
@@ -6841,9 +6888,9 @@ export type Query = {
readonly group_provenance?: Maybe<GroupProvenance>;
/** Get a pairwise polymeric interface given the PDB ID, ASSEMBLY ID and INTERFACE ID. */
readonly interface?: Maybe<CoreInterface>;
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. */
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. Note that the ENTRY ID part must be upper case. */
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. Note that the ENTRY ID part must be upper case. */
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
/** Get a PDB non-polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
readonly nonpolymer_entity?: Maybe<CoreNonpolymerEntity>;
@@ -6851,7 +6898,7 @@ export type Query = {
readonly nonpolymer_entity_instance?: Maybe<CoreNonpolymerEntityInstance>;
/** Get a list of PDB non-polymer entity instances (chains), given the list of ENTITY INSTANCE IDs. Here ENTITY INSTANCE ID identifies structural element in the asymmetric unit, e.g. 'A', 'B', etc. */
readonly nonpolymer_entity_instances?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntityInstance>>>;
/** Get a list of PDB polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
/** Get a list of PDB polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. Note that the ENTRY ID part must be upper case. */
readonly polymer_entities?: Maybe<ReadonlyArray<Maybe<CorePolymerEntity>>>;
/** Get a PDB polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
readonly polymer_entity?: Maybe<CorePolymerEntity>;
@@ -8421,6 +8468,29 @@ export type RcsbClusterMembership = {
readonly identity?: Maybe<Scalars['Int']>;
};
export type RcsbCompModelProvenance = {
/**
* Entry identifier corresponding to the computed structure model.
*
* Examples:
* AF-P60325-F1, ma-bak-cepc-0019
*
*/
readonly entry_id: Scalars['String'];
/**
* Source database for the computed structure model.
*
* Allowable values:
* AlphaFoldDB, ModelArchive
*
*/
readonly source_db?: Maybe<Scalars['String']>;
/** Source filename for the computed structure model. */
readonly source_filename?: Maybe<Scalars['String']>;
/** Source URL for computed structure model file. */
readonly source_url?: Maybe<Scalars['String']>;
};
export type RcsbEntityHostOrganism = {
/**
* The beginning polymer sequence position for the polymer section corresponding
@@ -8657,7 +8727,7 @@ export type RcsbEntitySourceOrganism = {
* A code indicating the provenance of the source organism details for the entity
*
* Allowable values:
* PDB Primary Data
* PDB Primary Data, UniProt
*
*/
readonly provenance_source?: Maybe<Scalars['String']>;
@@ -8731,7 +8801,7 @@ export type RcsbEntryContainerIdentifiers = {
* Entry identifier for the container.
*
* Examples:
* 1KIP, 4HHB
* 4HHB, AF_AFP60325F1, MA_MABAKCEPC0019
*
*/
readonly entry_id: Scalars['String'];
@@ -8879,6 +8949,15 @@ export type RcsbEntryInfo = {
*
*/
readonly na_polymer_entity_types?: Maybe<Scalars['String']>;
/**
* This data item identifies secondary structure
* features of nucleic acids in the entry.
*
* Allowable values:
* a-form double helix, b-form double helix, bulge loop, double helix, four-way junction, hairpin loop, internal loop, mismatched base pair, other right-handed double helix, parallel strands, quadruple helix, tetraloop, three-way junction, triple helix, two-way junction, z-form double helix
*
*/
readonly ndb_struct_conf_na_feature_combined?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
/** Bound nonpolymer components in this entry. */
readonly nonpolymer_bound_components?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
/** The number of distinct non-polymer entities in the structure entry exclusive of solvent. */
@@ -8955,6 +9034,21 @@ export type RcsbEntryInfo = {
readonly software_programs_combined?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
/** The number of distinct solvent entities per deposited structure model. */
readonly solvent_entity_count?: Maybe<Scalars['Int']>;
/**
* Indicates if the structure was determined using experimental or computational methods.
*
* Allowable values:
* computational, experimental
*
*/
readonly structure_determination_methodology: Scalars['String'];
/**
* Indicates the priority of the value in _rcsb_entry_info.structure_determination_methodology.
* The lower the number the higher the priority.
* Priority values for "experimental" structures is currently set to 10 and
* the values for "computational" structures is set to 100.
*/
readonly structure_determination_methodology_priority?: Maybe<Scalars['Int']>;
};
export type RcsbEntryInfoDiffrnResolutionHigh = {
@@ -9294,6 +9388,49 @@ export type RcsbLigandNeighbors = {
readonly seq_id?: Maybe<Scalars['Int']>;
};
export type RcsbMaQaMetricGlobal = {
readonly ma_qa_metric_global?: Maybe<ReadonlyArray<Maybe<RcsbMaQaMetricGlobalMaQaMetricGlobal>>>;
/** The model identifier. */
readonly model_id: Scalars['Int'];
};
export type RcsbMaQaMetricGlobalMaQaMetricGlobal = {
/**
* Description of the global QA metric.
*
* Examples:
* confidence score predicting accuracy according to the CA-only Local Distance Difference Test (lDDT-CA) in [0,100]
*
*/
readonly description?: Maybe<Scalars['String']>;
/**
* Name of the global QA metric.
*
* Examples:
* pLDDT
*
*/
readonly name: Scalars['String'];
/**
* The type of global QA metric.
*
* Allowable values:
* PAE, contact probability, distance, energy, ipTM, normalized score, other, pLDDT, pLDDT all-atom, pLDDT all-atom in [0,1], pLDDT in [0,1], pTM, zscore
*
*/
readonly type: Scalars['String'];
/** Details for other type of global QA metric. */
readonly type_other_details?: Maybe<Scalars['String']>;
/**
* Value of the global QA metric.
*
* Examples:
* null
*
*/
readonly value: Scalars['Float'];
};
export type RcsbMembraneLineage = {
/** Hierarchy depth. */
readonly depth?: Maybe<Scalars['Int']>;
@@ -10332,7 +10469,7 @@ export type RcsbPolymerEntityContainerIdentifiersReferenceSequenceIdentifiers =
* Source of the reference database assignment
*
* Allowable values:
* PDB, RCSB, SIFTS
* PDB, RCSB, SIFTS, UniProt
*
*/
readonly provenance_source?: Maybe<Scalars['String']>;
@@ -10737,7 +10874,7 @@ export type RcsbPolymerInstanceFeature = {
* A type or category of the feature.
*
* Allowable values:
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
* ANGLE_OUTLIER, BEND, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, HELX_LH_PP_P, HELX_RH_3T_P, HELX_RH_AL_P, HELX_RH_PI_P, MA_QA_METRIC_LOCAL_TYPE_CONTACT_PROBABILITY, MA_QA_METRIC_LOCAL_TYPE_DISTANCE, MA_QA_METRIC_LOCAL_TYPE_ENERGY, MA_QA_METRIC_LOCAL_TYPE_IPTM, MA_QA_METRIC_LOCAL_TYPE_NORMALIZED_SCORE, MA_QA_METRIC_LOCAL_TYPE_OTHER, MA_QA_METRIC_LOCAL_TYPE_PAE, MA_QA_METRIC_LOCAL_TYPE_PLDDT, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM_[0,1], MA_QA_METRIC_LOCAL_TYPE_PLDDT_[0,1], MA_QA_METRIC_LOCAL_TYPE_PTM, MA_QA_METRIC_LOCAL_TYPE_ZSCORE, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, STRN, TURN_TY1_P, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
*
*/
readonly type?: Maybe<Scalars['String']>;
@@ -10748,7 +10885,7 @@ export type RcsbPolymerInstanceFeatureAdditionalProperties = {
* The additional property name.
*
* Allowable values:
* CATH_DOMAIN_ID, CATH_NAME, ECOD_DOMAIN_ID, ECOD_FAMILY_NAME, OMEGA_ANGLE, PARTNER_ASYM_ID, PARTNER_BOND_DISTANCE, PARTNER_COMP_ID, SCOP2_DOMAIN_ID, SCOP2_FAMILY_ID, SCOP2_FAMILY_NAME, SCOP2_SUPERFAMILY_ID, SCOP2_SUPERFAMILY_NAME, SCOP_DOMAIN_ID, SCOP_NAME, SCOP_SUN_ID, SHEET_SENSE
* CATH_DOMAIN_ID, CATH_NAME, ECOD_DOMAIN_ID, ECOD_FAMILY_NAME, MODELCIF_MODEL_ID, OMEGA_ANGLE, PARTNER_ASYM_ID, PARTNER_BOND_DISTANCE, PARTNER_COMP_ID, SCOP2_DOMAIN_ID, SCOP2_FAMILY_ID, SCOP2_FAMILY_NAME, SCOP2_SUPERFAMILY_ID, SCOP2_SUPERFAMILY_NAME, SCOP_DOMAIN_ID, SCOP_NAME, SCOP_SUN_ID, SHEET_SENSE
*
*/
readonly name?: Maybe<Scalars['String']>;
@@ -10818,7 +10955,7 @@ export type RcsbPolymerInstanceFeatureSummary = {
* Type or category of the feature.
*
* Allowable values:
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SAbDab Antibody Heavy Chain Subclass, SAbDab Antibody Light Chain Subclass, SAbDab Antibody Light Chain Type, SCOP, SCOP2 Family, SCOP2 Superfamily, SCOP2B Superfamily, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
* ANGLE_OUTLIER, BEND, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, HELX_LH_PP_P, HELX_RH_3T_P, HELX_RH_AL_P, HELX_RH_PI_P, MA_QA_METRIC_LOCAL_TYPE_CONTACT_PROBABILITY, MA_QA_METRIC_LOCAL_TYPE_DISTANCE, MA_QA_METRIC_LOCAL_TYPE_ENERGY, MA_QA_METRIC_LOCAL_TYPE_IPTM, MA_QA_METRIC_LOCAL_TYPE_NORMALIZED_SCORE, MA_QA_METRIC_LOCAL_TYPE_OTHER, MA_QA_METRIC_LOCAL_TYPE_PAE, MA_QA_METRIC_LOCAL_TYPE_PLDDT, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM_[0,1], MA_QA_METRIC_LOCAL_TYPE_PLDDT_[0,1], MA_QA_METRIC_LOCAL_TYPE_PTM, MA_QA_METRIC_LOCAL_TYPE_ZSCORE, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, STRN, TURN_TY1_P, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
*
*/
readonly type?: Maybe<Scalars['String']>;
@@ -13377,6 +13514,11 @@ export type ReflnsShell = {
};
export type Software = {
/**
* This data item is a pointer to _citation.id in the CITATION
* category.
*/
readonly citation_id?: Maybe<Scalars['String']>;
/**
* The classification of the program according to its
* major function.
@@ -13527,6 +13669,37 @@ export type Struct = {
readonly title?: Maybe<Scalars['String']>;
};
export type StructAsym = {
/**
* This data item is a pointer to _atom_site.pdbx_PDB_strand_id the
* ATOM_SITE category.
*
* Examples:
* 1ABC
*
*/
readonly pdbx_PDB_id?: Maybe<Scalars['String']>;
/**
* This data item is a pointer to _atom_site.ndb_alias_strand_id the
* ATOM_SITE category.
*/
readonly pdbx_alt_id?: Maybe<Scalars['String']>;
/**
* This data item gives the order of the structural elements in the
* ATOM_SITE category.
*/
readonly pdbx_order?: Maybe<Scalars['Int']>;
/**
* This data item describes the general type of the structural elements
* in the ATOM_SITE category.
*
* Allowable values:
* ATOMN, ATOMP, ATOMS, HETAC, HETAD, HETAI, HETAIN, HETAS, HETIC
*
*/
readonly pdbx_type?: Maybe<Scalars['String']>;
};
export type StructKeywords = {
/**
* Terms characterizing the macromolecular structure.

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { PluginCommands } from '../../mol-plugin/commands';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { createMeshFromUrl } from '../meshes/examples';
import { BACKGROUND_SEGMENT_VOLUME_THRESHOLD } from '../meshes/mesh-streaming/behavior';
import { Segment } from './volseg-api/data';
import { VolsegEntryData } from './entry-root';
const DEFAULT_MESH_DETAIL: number | null = 5; // null means worst
export class VolsegMeshSegmentationData {
private entryData: VolsegEntryData;
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
async loadSegmentation() {
const hasMeshes = this.entryData.metadata.meshSegmentIds.length > 0;
if (hasMeshes) {
await this.showSegments(this.entryData.metadata.allSegmentIds);
}
}
updateOpacity(opacity: number) {
const visuals = this.entryData.findNodesByTags('mesh-segment-visual');
const update = this.entryData.newUpdate();
for (const visual of visuals) {
update.to(visual).update(ShapeRepresentation3D, p => { (p as any).alpha = opacity; });
}
return update.commit();
}
async highlightSegment(segment: Segment) {
const visuals = this.entryData.findNodesByTags('mesh-segment-visual', `segment-${segment.id}`);
for (const visual of visuals) {
await PluginCommands.Interactivity.Object.Highlight(this.entryData.plugin, { state: this.entryData.plugin.state.data, ref: visual.transform.ref });
}
}
async selectSegment(segment?: number) {
if (segment === undefined || segment < 0) return;
const visuals = this.entryData.findNodesByTags('mesh-segment-visual', `segment-${segment}`);
const reprNode: PluginStateObject.Shape.Representation3D | undefined = visuals[0]?.obj;
if (!reprNode) return;
const loci = reprNode.data.repr.getAllLoci()[0];
if (!loci) return;
this.entryData.plugin.managers.interactivity.lociSelects.select({ loci: loci, repr: reprNode.data.repr }, false);
}
/** Make visible the specified set of mesh segments */
async showSegments(segments: number[]) {
const segmentsToShow = new Set(segments);
const visuals = this.entryData.findNodesByTags('mesh-segment-visual');
for (const visual of visuals) {
const theTag = visual.obj?.tags?.find(tag => tag.startsWith('segment-'));
if (!theTag) continue;
const id = parseInt(theTag.split('-')[1]);
const visibility = segmentsToShow.has(id);
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, !visibility); // true means hide, ¯\_(ツ)_/¯
segmentsToShow.delete(id);
}
const segmentsToCreate = this.entryData.metadata.meshSegmentIds.filter(seg => segmentsToShow.has(seg));
if (segmentsToCreate.length === 0) return;
let group = this.entryData.findNodesByTags('mesh-segmentation-group')[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup, { label: 'Segmentation', description: 'Mesh' }, { tags: ['mesh-segmentation-group'], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const totalVolume = this.entryData.metadata.gridTotalVolume;
const awaiting = [];
for (const seg of segmentsToCreate) {
const segment = this.entryData.metadata.getSegment(seg);
if (!segment) continue;
const detail = this.entryData.metadata.getSufficientMeshDetail(seg, DEFAULT_MESH_DETAIL);
const color = segment.colour.length >= 3 ? Color.fromNormalizedArray(segment.colour, 0) : ColorNames.gray;
const url = this.entryData.api.meshUrl_Bcif(this.entryData.source, this.entryData.entryId, seg, detail);
const label = segment.biological_annotation.name ?? `Segment ${seg}`;
const meshPromise = createMeshFromUrl(this.entryData.plugin, url, seg, detail, true, color, group,
BACKGROUND_SEGMENT_VOLUME_THRESHOLD * totalVolume, `<b>${label}</b>`, this.entryData.ref);
awaiting.push(meshPromise);
}
for (const promise of awaiting) await promise;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { TrajectoryFromMmCif } from '../../mol-plugin-state/transforms/model';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { VolsegEntryData } from './entry-root';
export class VolsegModelData {
private entryData: VolsegEntryData;
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
private async loadPdb(pdbId: string, parent: StateObjectSelector | StateObjectRef) {
const url = `https://www.ebi.ac.uk/pdbe/entry-files/download/${pdbId}.bcif`;
const dataNode = await this.entryData.plugin.build().to(parent).apply(Download, { url: url, isBinary: true }, { tags: ['fitted-model-data', `pdbid-${pdbId}`] }).commit();
const cifNode = await this.entryData.plugin.build().to(dataNode).apply(ParseCif).commit();
const trajectoryNode = await this.entryData.plugin.build().to(cifNode).apply(TrajectoryFromMmCif).commit();
await this.entryData.plugin.builders.structure.hierarchy.applyPreset(trajectoryNode, 'default', { representationPreset: 'polymer-cartoon' });
return dataNode;
}
async showPdbs(pdbIds: string[]) {
const segmentsToShow = new Set(pdbIds);
const visuals = this.entryData.findNodesByTags('fitted-model-data');
for (const visual of visuals) {
const theTag = visual.obj?.tags?.find(tag => tag.startsWith('pdbid-'));
if (!theTag) continue;
const id = theTag.split('-')[1];
const visibility = segmentsToShow.has(id);
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, !visibility); // true means hide, ¯\_(ツ)_/¯
segmentsToShow.delete(id);
}
const segmentsToCreate = Array.from(segmentsToShow);
if (segmentsToCreate.length === 0) return;
let group = this.entryData.findNodesByTags('fitted-models-group')[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup, { label: 'Fitted Models' }, { tags: ['fitted-models-group'], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const awaiting = [];
for (const pdbId of segmentsToCreate) {
awaiting.push(this.loadPdb(pdbId, group));
}
for (const promise of awaiting) await promise;
}
}

View File

@@ -0,0 +1,377 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { BehaviorSubject, distinctUntilChanged, Subject, throttleTime } from 'rxjs';
import { VolsegVolumeServerConfig } from '.';
import { Loci } from '../../mol-model/loci';
import { ShapeGroup } from '../../mol-model/shape';
import { Volume } from '../../mol-model/volume';
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
import { shallowEqualObjects } from '../../mol-util';
import { ParamDefinition } from '../../mol-util/param-definition';
import { MeshlistData } from '../meshes/mesh-extension';
import { DEFAULT_VOLSEG_SERVER, VolumeApiV2 } from './volseg-api/api';
import { Segment } from './volseg-api/data';
import { MetadataWrapper } from './volseg-api/utils';
import { VolsegMeshSegmentationData } from './entry-meshes';
import { VolsegModelData } from './entry-models';
import { VolsegLatticeSegmentationData } from './entry-segmentation';
import { VolsegState, VolsegStateData, VolsegStateParams } from './entry-state';
import { VolsegVolumeData, SimpleVolumeParamValues, VOLUME_VISUAL_TAG } from './entry-volume';
import * as ExternalAPIs from './external-api';
import { VolsegGlobalStateData } from './global-state';
import { applyEllipsis, Choice, isDefined, lazyGetter, splitEntryId } from './helpers';
import { type VolsegStateFromEntry } from './transformers';
import { StateTransforms } from '../../mol-plugin-state/transforms';
export const MAX_VOXELS = 10 ** 7;
// export const MAX_VOXELS = 10 ** 2; // DEBUG
export const BOX: [[number, number, number], [number, number, number]] | null = null;
// export const BOX: [[number, number, number], [number, number, number]] | null = [[-90, -90, -90], [90, 90, 90]]; // DEBUG
const MAX_ANNOTATIONS_IN_LABEL = 6;
const SourceChoice = new Choice({ emdb: 'EMDB', empiar: 'EMPIAR', idr: 'IDR' }, 'emdb');
export type Source = Choice.Values<typeof SourceChoice>;
export function createLoadVolsegParams(plugin?: PluginContext, entrylists: { [source: string]: string[] } = {}) {
const defaultVolumeServer = plugin?.config.get(VolsegVolumeServerConfig.DefaultServer) ?? DEFAULT_VOLSEG_SERVER;
return {
serverUrl: ParamDefinition.Text(defaultVolumeServer),
source: ParamDefinition.Mapped(SourceChoice.values[0], SourceChoice.options, src => entryParam(entrylists[src])),
};
}
function entryParam(entries: string[] = []) {
const options: [string, string][] = entries.map(e => [e, e]);
options.push(['__custom__', 'Custom']);
return ParamDefinition.Group({
entryId: ParamDefinition.Select(options[0][0], options, { description: 'Choose an entry from the list, or choose "Custom" and type any entry ID (useful when using other than default server).' }),
customEntryId: ParamDefinition.Text('', { hideIf: p => p.entryId !== '__custom__', description: 'Entry identifier, including the source prefix, e.g. "emd-1832"' }),
}, { isFlat: true });
}
type LoadVolsegParamValues = ParamDefinition.Values<ReturnType<typeof createLoadVolsegParams>>;
export function createVolsegEntryParams(plugin?: PluginContext) {
const defaultVolumeServer = plugin?.config.get(VolsegVolumeServerConfig.DefaultServer) ?? DEFAULT_VOLSEG_SERVER;
return {
serverUrl: ParamDefinition.Text(defaultVolumeServer),
source: SourceChoice.PDSelect(),
entryId: ParamDefinition.Text('emd-1832', { description: 'Entry identifier, including the source prefix, e.g. "emd-1832"' }),
};
}
type VolsegEntryParamValues = ParamDefinition.Values<ReturnType<typeof createVolsegEntryParams>>;
export namespace VolsegEntryParamValues {
export function fromLoadVolsegParamValues(params: LoadVolsegParamValues): VolsegEntryParamValues {
let entryId = (params.source.params as any).entryId;
if (entryId === '__custom__') {
entryId = (params.source.params as any).customEntryId;
}
return {
serverUrl: params.serverUrl,
source: params.source.name as Source,
entryId: entryId
};
}
}
export class VolsegEntry extends PluginStateObject.CreateBehavior<VolsegEntryData>({ name: 'Vol & Seg Entry' }) { }
type VolRepr3DT = typeof StateTransforms.Representation.VolumeRepresentation3D
export class VolsegEntryData extends PluginBehavior.WithSubscribers<VolsegEntryParamValues> {
plugin: PluginContext;
ref: string = '';
api: VolumeApiV2;
source: Source;
/** Number part of entry ID; e.g. '1832' */
entryNumber: string;
/** Full entry ID; e.g. 'emd-1832' */
entryId: string;
metadata: MetadataWrapper;
pdbs: string[];
public readonly volumeData = new VolsegVolumeData(this);
private readonly latticeSegmentationData = new VolsegLatticeSegmentationData(this);
private readonly meshSegmentationData = new VolsegMeshSegmentationData(this);
private readonly modelData = new VolsegModelData(this);
private highlightRequest = new Subject<Segment | undefined>();
private getStateNode = lazyGetter(() => this.plugin.state.data.selectQ(q => q.byRef(this.ref).subtree().ofType(VolsegState))[0] as StateObjectCell<VolsegState, StateTransform<typeof VolsegStateFromEntry>>, 'Missing VolsegState node. Must first create VolsegState for this VolsegEntry.');
public currentState = new BehaviorSubject(ParamDefinition.getDefaultValues(VolsegStateParams));
public currentVolume = new BehaviorSubject<StateTransform<VolRepr3DT> | undefined>(undefined);
private constructor(plugin: PluginContext, params: VolsegEntryParamValues) {
super(plugin, params);
this.plugin = plugin;
this.api = new VolumeApiV2(params.serverUrl);
this.source = params.source;
this.entryId = params.entryId;
this.entryNumber = splitEntryId(this.entryId).entryNumber;
}
private async initialize() {
const metadata = await this.api.getMetadata(this.source, this.entryId);
this.metadata = new MetadataWrapper(metadata);
this.pdbs = await ExternalAPIs.getPdbIdsForEmdbEntry(this.metadata.raw.grid.general.source_db_id ?? this.entryId);
// TODO use Asset?
}
static async create(plugin: PluginContext, params: VolsegEntryParamValues) {
const result = new VolsegEntryData(plugin, params);
await result.initialize();
return result;
}
async register(ref: string) {
this.ref = ref;
this.plugin.managers.lociLabels.addProvider(this.labelProvider);
try {
const params = this.getStateNode().obj?.data;
if (params) {
this.currentState.next(params);
}
} catch {
// do nothing
}
const volumeVisual = this.findNodesByTags(VOLUME_VISUAL_TAG)[0];
if (volumeVisual) this.currentVolume.next(volumeVisual.transform);
let volumeRef: string | undefined;
this.subscribeObservable(this.plugin.state.data.events.cell.stateUpdated, e => {
try { (this.getStateNode()); } catch { return; } // if state not does not exist yet
if (e.cell.transform.ref === this.getStateNode().transform.ref) {
const newState = this.getStateNode().obj?.data;
if (newState && !shallowEqualObjects(newState, this.currentState.value)) { // avoid repeated update
this.currentState.next(newState);
}
} else if (e.cell.transform.tags?.includes(VOLUME_VISUAL_TAG)) {
if (e.ref === volumeRef) {
this.currentVolume.next(e.cell.transform);
} else if (StateSelection.findAncestor(this.plugin.state.data.tree, this.plugin.state.data.cells, e.ref, a => a.transform.ref === ref)) {
volumeRef = e.ref;
this.currentVolume.next(e.cell.transform);
}
}
});
this.subscribeObservable(this.plugin.state.data.events.cell.removed, e => {
if (e.ref === volumeRef) {
volumeRef = undefined;
this.currentVolume.next(undefined);
}
});
this.subscribeObservable(this.plugin.behaviors.interaction.click, async e => {
const loci = e.current.loci;
const clickedSegment = this.getSegmentIdFromLoci(loci);
if (clickedSegment === undefined) return;
if (clickedSegment === this.currentState.value.selectedSegment) {
this.actionSelectSegment(undefined);
} else {
this.actionSelectSegment(clickedSegment);
}
});
this.subscribeObservable(
this.highlightRequest.pipe(throttleTime(50, undefined, { leading: true, trailing: true })),
async segment => await this.highlightSegment(segment)
);
this.subscribeObservable(
this.currentState.pipe(distinctUntilChanged((a, b) => a.selectedSegment === b.selectedSegment)),
async state => {
if (VolsegGlobalStateData.getGlobalState(this.plugin)?.selectionMode) await this.selectSegment(state.selectedSegment);
}
);
}
async unregister() {
this.plugin.managers.lociLabels.removeProvider(this.labelProvider);
}
async loadVolume() {
const result = await this.volumeData.loadVolume();
if (result) {
const isovalue = result.isovalue.kind === 'relative' ? result.isovalue.relativeValue : result.isovalue.absoluteValue;
await this.updateStateNode({ volumeIsovalueKind: result.isovalue.kind, volumeIsovalueValue: isovalue });
}
}
async loadSegmentations() {
await this.latticeSegmentationData.loadSegmentation();
await this.meshSegmentationData.loadSegmentation();
await this.actionShowSegments(this.metadata.allSegmentIds);
}
actionHighlightSegment(segment?: Segment) {
this.highlightRequest.next(segment);
}
async actionToggleSegment(segment: number) {
const current = this.currentState.value.visibleSegments.map(seg => seg.segmentId);
if (current.includes(segment)) {
await this.actionShowSegments(current.filter(s => s !== segment));
} else {
await this.actionShowSegments([...current, segment]);
}
}
async actionToggleAllSegments() {
const current = this.currentState.value.visibleSegments.map(seg => seg.segmentId);
if (current.length !== this.metadata.allSegments.length) {
await this.actionShowSegments(this.metadata.allSegmentIds);
} else {
await this.actionShowSegments([]);
}
}
async actionSelectSegment(segment?: number) {
if (segment !== undefined && this.currentState.value.visibleSegments.find(s => s.segmentId === segment) === undefined) {
// first make the segment visible if it is not
await this.actionToggleSegment(segment);
}
await this.updateStateNode({ selectedSegment: segment });
}
async actionSetOpacity(opacity: number) {
if (opacity === this.getStateNode().obj?.data.segmentOpacity) return;
this.latticeSegmentationData.updateOpacity(opacity);
this.meshSegmentationData.updateOpacity(opacity);
await this.updateStateNode({ segmentOpacity: opacity });
}
async actionShowFittedModel(pdbIds: string[]) {
await this.modelData.showPdbs(pdbIds);
await this.updateStateNode({ visibleModels: pdbIds.map(pdbId => ({ pdbId: pdbId })) });
}
async actionSetVolumeVisual(type: 'isosurface' | 'direct-volume' | 'off') {
await this.volumeData.setVolumeVisual(type);
await this.updateStateNode({ volumeType: type });
}
async actionUpdateVolumeVisual(params: SimpleVolumeParamValues) {
await this.volumeData.updateVolumeVisual(params);
await this.updateStateNode({
volumeType: params.volumeType,
volumeOpacity: params.opacity,
});
}
private async actionShowSegments(segments: number[]) {
await this.latticeSegmentationData.showSegments(segments);
await this.meshSegmentationData.showSegments(segments);
await this.updateStateNode({ visibleSegments: segments.map(s => ({ segmentId: s })) });
}
private async highlightSegment(segment?: Segment) {
await PluginCommands.Interactivity.ClearHighlights(this.plugin);
if (segment) {
await this.latticeSegmentationData.highlightSegment(segment);
await this.meshSegmentationData.highlightSegment(segment);
}
}
private async selectSegment(segment: number) {
this.plugin.managers.interactivity.lociSelects.deselectAll();
await this.latticeSegmentationData.selectSegment(segment);
await this.meshSegmentationData.selectSegment(segment);
await this.highlightSegment();
}
private async updateStateNode(params: Partial<VolsegStateData>) {
const oldParams = this.getStateNode().transform.params;
const newParams = { ...oldParams, ...params };
const state = this.plugin.state.data;
const update = state.build().to(this.getStateNode().transform.ref).update(newParams);
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
/** Find the nodes under this entry root which have all of the given tags. */
findNodesByTags(...tags: string[]) {
return this.plugin.state.data.selectQ(q => {
let builder = q.byRef(this.ref).subtree();
for (const tag of tags) builder = builder.withTag(tag);
return builder;
});
}
newUpdate() {
if (this.ref !== '') {
return this.plugin.build().to(this.ref);
} else {
return this.plugin.build().toRoot();
}
}
private readonly labelProvider: LociLabelProvider = {
label: (loci: Loci): string | undefined => {
const segmentId = this.getSegmentIdFromLoci(loci);
if (segmentId === undefined) return;
const segment = this.metadata.getSegment(segmentId);
if (!segment) return;
const annotLabels = segment.biological_annotation.external_references.map(annot => `${applyEllipsis(annot.label)} [${annot.resource}:${annot.accession}]`);
if (annotLabels.length === 0) return;
if (annotLabels.length > MAX_ANNOTATIONS_IN_LABEL + 1) {
const nHidden = annotLabels.length - MAX_ANNOTATIONS_IN_LABEL;
annotLabels.length = MAX_ANNOTATIONS_IN_LABEL;
annotLabels.push(`(${nHidden} more annotations, click on the segment to see all)`);
}
return '<hr class="msp-highlight-info-hr"/>' + annotLabels.filter(isDefined).join('<br/>');
}
};
private getSegmentIdFromLoci(loci: Loci): number | undefined {
if (Volume.Segment.isLoci(loci) && loci.volume._propertyData.ownerId === this.ref) {
if (loci.segments.length === 1) {
return loci.segments[0];
}
}
if (ShapeGroup.isLoci(loci)) {
const meshData = (loci.shape.sourceData ?? {}) as MeshlistData;
if (meshData.ownerId === this.ref && meshData.segmentId !== undefined) {
return meshData.segmentId;
}
}
}
async setTryUseGpu(tryUseGpu: boolean) {
await Promise.all([
this.volumeData.setTryUseGpu(tryUseGpu),
this.latticeSegmentationData.setTryUseGpu(tryUseGpu),
]);
}
async setSelectionMode(selectSegments: boolean) {
if (selectSegments) {
await this.selectSegment(this.currentState.value.selectedSegment);
} else {
this.plugin.managers.interactivity.lociSelects.deselectAll();
}
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Volume } from '../../mol-model/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { VolumeFromSegmentationCif } from '../../mol-plugin-state/transforms/volume';
import { PluginCommands } from '../../mol-plugin/commands';
import { Color } from '../../mol-util/color';
import { Segment } from './volseg-api/data';
import { BOX, VolsegEntryData, MAX_VOXELS } from './entry-root';
import { VolumeVisualParams } from './entry-volume';
import { VolsegGlobalStateData } from './global-state';
const GROUP_TAG = 'lattice-segmentation-group';
const SEGMENT_VISUAL_TAG = 'lattice-segment-visual';
const DEFAULT_SEGMENT_COLOR = Color.fromNormalizedRgb(0.8, 0.8, 0.8);
export class VolsegLatticeSegmentationData {
private entryData: VolsegEntryData;
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
async loadSegmentation() {
const hasLattices = this.entryData.metadata.raw.grid.segmentation_lattices.segmentation_lattice_ids.length > 0;
if (hasLattices) {
const url = this.entryData.api.latticeUrl(this.entryData.source, this.entryData.entryId, 0, BOX, MAX_VOXELS);
let group = this.entryData.findNodesByTags(GROUP_TAG)[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup,
{ label: 'Segmentation', description: 'Lattice' }, { tags: [GROUP_TAG], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const segmentLabels = this.entryData.metadata.allSegments.map(seg => ({ id: seg.id, label: seg.biological_annotation.name ? `<b>${seg.biological_annotation.name}</b>` : '' }));
const volumeNode = await this.entryData.newUpdate().to(group)
.apply(Download, { url, isBinary: true, label: `Segmentation Data: ${url}` })
.apply(ParseCif)
.apply(VolumeFromSegmentationCif, { blockHeader: 'SEGMENTATION_DATA', segmentLabels: segmentLabels, ownerId: this.entryData.ref })
.commit();
const volumeData = volumeNode.data as Volume;
const segmentation = Volume.Segmentation.get(volumeData);
const segmentIds: number[] = Array.from(segmentation?.segments.keys() ?? []);
await this.entryData.newUpdate().to(volumeNode)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.entryData.plugin, volumeData, {
type: 'segment',
typeParams: { tryUseGpu: VolsegGlobalStateData.getGlobalState(this.entryData.plugin)?.tryUseGpu },
color: 'volume-segment',
colorParams: { palette: this.createPalette(segmentIds) },
}), { tags: [SEGMENT_VISUAL_TAG] }).commit();
}
}
private createPalette(segmentIds: number[]) {
const colorMap = new Map<number, Color>();
for (const segment of this.entryData.metadata.allSegments) {
const color = Color.fromNormalizedArray(segment.colour, 0);
colorMap.set(segment.id, color);
}
if (colorMap.size === 0) return undefined;
for (const segid of segmentIds) {
colorMap.get(segid);
}
const colors = segmentIds.map(segid => colorMap.get(segid) ?? DEFAULT_SEGMENT_COLOR);
return { name: 'colors' as const, params: { list: { kind: 'set' as const, colors: colors } } };
}
async updateOpacity(opacity: number) {
const reprs = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG);
const update = this.entryData.newUpdate();
for (const s of reprs) {
update.to(s).update(StateTransforms.Representation.VolumeRepresentation3D, p => { p.type.params.alpha = opacity; });
}
return await update.commit();
}
private makeLoci(segments: number[]) {
const vis = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG)[0];
if (!vis) return undefined;
const repr = vis.obj?.data.repr;
const wholeLoci = repr.getAllLoci()[0];
if (!wholeLoci || !Volume.Segment.isLoci(wholeLoci)) return undefined;
return { loci: Volume.Segment.Loci(wholeLoci.volume, segments), repr: repr };
}
async highlightSegment(segment: Segment) {
const segmentLoci = this.makeLoci([segment.id]);
if (!segmentLoci) return;
this.entryData.plugin.managers.interactivity.lociHighlights.highlight(segmentLoci, false);
}
async selectSegment(segment?: number) {
if (segment === undefined || segment < 0) return;
const segmentLoci = this.makeLoci([segment]);
if (!segmentLoci) return;
this.entryData.plugin.managers.interactivity.lociSelects.select(segmentLoci, false);
}
/** Make visible the specified set of lattice segments */
async showSegments(segments: number[]) {
const repr = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG)[0];
if (!repr) return;
const selectedSegment = this.entryData.currentState.value.selectedSegment;
const mustReselect = segments.includes(selectedSegment) && !repr.params?.values.type.params.segments.includes(selectedSegment);
const update = this.entryData.newUpdate();
update.to(repr).update(StateTransforms.Representation.VolumeRepresentation3D, p => { p.type.params.segments = segments; });
await update.commit();
if (mustReselect) {
await this.selectSegment(this.entryData.currentState.value.selectedSegment);
}
}
async setTryUseGpu(tryUseGpu: boolean) {
const visuals = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG);
for (const visual of visuals) {
const oldParams: VolumeVisualParams = visual.transform.params;
if (oldParams.type.params.tryUseGpu === !tryUseGpu) {
const newParams = { ...oldParams, type: { ...oldParams.type, params: { ...oldParams.type.params, tryUseGpu: tryUseGpu } } };
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Choice } from './helpers';
export const VolumeTypeChoice = new Choice({ 'isosurface': 'Isosurface', 'direct-volume': 'Direct volume', 'off': 'Off' }, 'isosurface');
export type VolumeType = Choice.Values<typeof VolumeTypeChoice>
export const VolsegStateParams = {
volumeType: VolumeTypeChoice.PDSelect(),
volumeIsovalueKind: PD.Select('relative', [['relative', 'Relative'], ['absolute', 'Absolute']]),
volumeIsovalueValue: PD.Numeric(1),
volumeOpacity: PD.Numeric(0.2, { min: 0, max: 1, step: 0.05 }),
segmentOpacity: PD.Numeric(1, { min: 0, max: 1, step: 0.05 }),
selectedSegment: PD.Numeric(-1, { step: 1 }),
visibleSegments: PD.ObjectList({ segmentId: PD.Numeric(0) }, s => s.segmentId.toString()),
visibleModels: PD.ObjectList({ pdbId: PD.Text('') }, s => s.pdbId.toString()),
};
export type VolsegStateData = PD.Values<typeof VolsegStateParams>;
export class VolsegState extends PluginStateObject.Create<VolsegStateData>({ name: 'Vol & Seg Entry State', typeClass: 'Data' }) { }
export const VOLSEG_STATE_FROM_ENTRY_TRANSFORMER_NAME = 'volseg-state-from-entry'; // defined here to avoid cyclic dependency

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Vec2 } from '../../mol-math/linear-algebra';
import { Volume } from '../../mol-model/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { Download } from '../../mol-plugin-state/transforms/data';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { PluginCommands } from '../../mol-plugin/commands';
import { StateObjectSelector } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { BOX, VolsegEntryData, MAX_VOXELS } from './entry-root';
import { VolsegStateParams, VolumeTypeChoice } from './entry-state';
import * as ExternalAPIs from './external-api';
import { VolsegGlobalStateData } from './global-state';
const GROUP_TAG = 'volume-group';
export const VOLUME_VISUAL_TAG = 'volume-visual';
const DIRECT_VOLUME_RELATIVE_PEAK_HALFWIDTH = 0.5;
export type VolumeVisualParams = ReturnType<typeof createVolumeRepresentationParams>;
interface VolumeStats { min: number, max: number, mean: number, sigma: number };
export const SimpleVolumeParams = {
volumeType: VolumeTypeChoice.PDSelect(),
opacity: PD.Numeric(0.2, { min: 0, max: 1, step: 0.05 }, { hideIf: p => p.volumeType === 'off' }),
};
export type SimpleVolumeParamValues = PD.Values<typeof SimpleVolumeParams>;
export class VolsegVolumeData {
private entryData: VolsegEntryData;
private visualTypeParamCache: { [type: string]: any } = {};
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
async loadVolume() {
const hasVolumes = this.entryData.metadata.raw.grid.volumes.volume_downsamplings.length > 0;
if (hasVolumes) {
const isoLevelPromise = ExternalAPIs.tryGetIsovalue(this.entryData.metadata.raw.grid.general.source_db_id ?? this.entryData.entryId);
let group = this.entryData.findNodesByTags(GROUP_TAG)[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup, { label: 'Volume' }, { tags: [GROUP_TAG], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const url = this.entryData.api.volumeUrl(this.entryData.source, this.entryData.entryId, BOX, MAX_VOXELS);
const data = await this.entryData.newUpdate().to(group).apply(Download, { url, isBinary: true, label: `Volume Data: ${url}` }).commit();
const parsed = await this.entryData.plugin.dataFormats.get('dscif')!.parse(this.entryData.plugin, data);
const volumeNode: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[0] ?? parsed.volume;
const volumeData = volumeNode.cell!.obj!.data;
const volumeType = VolsegStateParams.volumeType.defaultValue;
let isovalue = await isoLevelPromise;
if (!isovalue) {
const stats = volumeData.grid.stats;
const maxRelative = (stats.max - stats.mean) / stats.sigma;
if (maxRelative > 1) {
isovalue = { kind: 'relative', value: 1.0 };
} else {
isovalue = { kind: 'relative', value: maxRelative * 0.5 };
}
}
const adjustedIsovalue = Volume.adjustedIsoValue(volumeData, isovalue.value, isovalue.kind);
const visualParams = this.createVolumeVisualParams(volumeData, volumeType);
this.changeIsovalueInVolumeVisualParams(visualParams, adjustedIsovalue, volumeData.grid.stats);
await this.entryData.newUpdate()
.to(volumeNode)
.apply(StateTransforms.Representation.VolumeRepresentation3D, visualParams, { tags: [VOLUME_VISUAL_TAG], state: { isHidden: volumeType === 'off' } })
.commit();
return { isovalue: adjustedIsovalue };
}
}
async setVolumeVisual(type: 'isosurface' | 'direct-volume' | 'off') {
const visual = this.entryData.findNodesByTags(VOLUME_VISUAL_TAG)[0];
if (!visual) return;
const oldParams: VolumeVisualParams = visual.transform.params;
this.visualTypeParamCache[oldParams.type.name] = oldParams.type.params;
if (type === 'off') {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, true); // true means hide, ¯\_(ツ)_/¯
} else {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, false); // true means hide, ¯\_(ツ)_/¯
if (oldParams.type.name === type) return;
const newParams: VolumeVisualParams = {
...oldParams,
type: {
name: type,
params: this.visualTypeParamCache[type] ?? oldParams.type.params,
}
};
const volumeStats = visual.obj?.data.sourceData.grid.stats;
if (!volumeStats) throw new Error(`Cannot get volume stats from volume visual ${visual.transform.ref}`);
this.changeIsovalueInVolumeVisualParams(newParams, undefined, volumeStats);
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
async updateVolumeVisual(newParams: SimpleVolumeParamValues) {
const { volumeType, opacity } = newParams;
const visual = this.entryData.findNodesByTags(VOLUME_VISUAL_TAG)[0];
if (!visual) return;
const oldVisualParams: VolumeVisualParams = visual.transform.params;
this.visualTypeParamCache[oldVisualParams.type.name] = oldVisualParams.type.params;
if (volumeType === 'off') {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, true); // true means hide, ¯\_(ツ)_/¯
} else {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, false); // true means hide, ¯\_(ツ)_/¯
const newVisualParams: VolumeVisualParams = {
...oldVisualParams,
type: {
name: volumeType,
params: this.visualTypeParamCache[volumeType] ?? oldVisualParams.type.params,
}
};
newVisualParams.type.params.alpha = opacity;
const volumeStats = visual.obj?.data.sourceData.grid.stats;
if (!volumeStats) throw new Error(`Cannot get volume stats from volume visual ${visual.transform.ref}`);
this.changeIsovalueInVolumeVisualParams(newVisualParams, undefined, volumeStats);
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newVisualParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
async setTryUseGpu(tryUseGpu: boolean) {
const visuals = this.entryData.findNodesByTags(VOLUME_VISUAL_TAG);
for (const visual of visuals) {
const oldParams: VolumeVisualParams = visual.transform.params;
if (oldParams.type.params.tryUseGpu === !tryUseGpu) {
const newParams = { ...oldParams, type: { ...oldParams.type, params: { ...oldParams.type.params, tryUseGpu: tryUseGpu } } };
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
}
private getIsovalueFromState(): Volume.IsoValue {
const { volumeIsovalueKind, volumeIsovalueValue } = this.entryData.currentState.value;
return volumeIsovalueKind === 'relative'
? Volume.IsoValue.relative(volumeIsovalueValue)
: Volume.IsoValue.absolute(volumeIsovalueValue);
}
private createVolumeVisualParams(volume: Volume, type: 'isosurface' | 'direct-volume' | 'off'): VolumeVisualParams {
if (type === 'off') type = 'isosurface';
return createVolumeRepresentationParams(this.entryData.plugin, volume, {
type: type,
typeParams: { alpha: 0.2, tryUseGpu: VolsegGlobalStateData.getGlobalState(this.entryData.plugin)?.tryUseGpu },
color: 'uniform',
colorParams: { value: Color(0x121212) },
});
}
private changeIsovalueInVolumeVisualParams(params: VolumeVisualParams, isovalue: Volume.IsoValue | undefined, stats: VolumeStats) {
isovalue ??= this.getIsovalueFromState();
switch (params.type.name) {
case 'isosurface':
params.type.params.isoValue = isovalue;
params.type.params.tryUseGpu = VolsegGlobalStateData.getGlobalState(this.entryData.plugin)?.tryUseGpu;
break;
case 'direct-volume':
const absIso = Volume.IsoValue.toAbsolute(isovalue, stats).absoluteValue;
const fractIso = (absIso - stats.min) / (stats.max - stats.min);
const peakHalfwidth = DIRECT_VOLUME_RELATIVE_PEAK_HALFWIDTH * stats.sigma / (stats.max - stats.min);
params.type.params.controlPoints = [
Vec2.create(Math.max(fractIso - peakHalfwidth, 0), 0),
Vec2.create(fractIso, 1),
Vec2.create(Math.min(fractIso + peakHalfwidth, 1), 0),
];
break;
}
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { splitEntryId } from './helpers';
/** Try to get author-defined contour value for isosurface from EMDB API. Return relative value 1.0, if not applicable or fails. */
export async function tryGetIsovalue(entryId: string): Promise<{ kind: 'absolute' | 'relative', value: number } | undefined> {
const split = splitEntryId(entryId);
if (split.source === 'emdb') {
try {
const response = await fetch(`https://www.ebi.ac.uk/emdb/api/entry/map/${split.entryNumber}`);
const json = await response.json();
const contours: any[] = json?.map?.contour_list?.contour;
if (contours && contours.length > 0) {
const theContour = contours.find(c => c.primary) || contours[0];
if (theContour.level === undefined) throw new Error('EMDB API response missing contour level.');
return { kind: 'absolute', value: theContour.level };
}
} catch {
// do nothing
}
}
return undefined;
}
export async function getPdbIdsForEmdbEntry(entryId: string): Promise<string[]> {
const split = splitEntryId(entryId);
const result = [];
if (split.source === 'emdb') {
entryId = entryId.toUpperCase();
const apiUrl = `https://www.ebi.ac.uk/pdbe/api/emdb/entry/fitted/${entryId}`;
try {
const response = await fetch(apiUrl);
if (response.ok) {
const json = await response.json();
const jsonEntry = json[entryId] ?? [];
for (const record of jsonEntry) {
const pdbs = record?.fitted_emdb_id_list?.pdb_id ?? [];
result.push(...pdbs);
}
}
} catch (ex) {
// do nothing
}
}
return result;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { BehaviorSubject } from 'rxjs';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginContext } from '../../mol-plugin/context';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { VolsegEntry } from './entry-root';
import { isDefined } from './helpers';
export const VolsegGlobalStateParams = {
tryUseGpu: PD.Boolean(true, { description: 'Attempt using GPU for faster rendering. \nCaution: with some hardware setups, this might render some objects incorrectly or not at all.' }),
selectionMode: PD.Boolean(true, { description: 'Allow selecting/deselecting a segment by clicking on it.' }),
};
export type VolsegGlobalStateParamValues = PD.Values<typeof VolsegGlobalStateParams>;
export class VolsegGlobalState extends PluginStateObject.CreateBehavior<VolsegGlobalStateData>({ name: 'Vol & Seg Global State' }) { }
export class VolsegGlobalStateData extends PluginBehavior.WithSubscribers<VolsegGlobalStateParamValues> {
private ref: string;
currentState = new BehaviorSubject(PD.getDefaultValues(VolsegGlobalStateParams));
constructor(plugin: PluginContext, params: VolsegGlobalStateParamValues) {
super(plugin, params);
this.currentState.next(params);
}
register(ref: string) {
this.ref = ref;
}
unregister() {
this.ref = '';
}
isRegistered() {
return this.ref !== '';
}
async updateState(plugin: PluginContext, state: Partial<VolsegGlobalStateParamValues>) {
const oldState = this.currentState.value;
const promises = [];
const allEntries = plugin.state.data.selectQ(q => q.ofType(VolsegEntry)).map(cell => cell.obj?.data).filter(isDefined);
if (state.tryUseGpu !== undefined && state.tryUseGpu !== oldState.tryUseGpu) {
for (const entry of allEntries) {
promises.push(entry.setTryUseGpu(state.tryUseGpu));
}
}
if (state.selectionMode !== undefined && state.selectionMode !== oldState.selectionMode) {
for (const entry of allEntries) {
promises.push(entry.setSelectionMode(state.selectionMode));
}
}
await Promise.all(promises);
await plugin.build().to(this.ref).update(state).commit();
}
static getGlobalState(plugin: PluginContext): VolsegGlobalStateParamValues | undefined {
return plugin.state.data.selectQ(q => q.ofType(VolsegGlobalState))[0]?.obj?.data.currentState.value;
}
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Volume } from '../../mol-model/volume';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { StateBuilder, StateObjectSelector, StateTransformer } from '../../mol-state';
import { ParamDefinition } from '../../mol-util/param-definition';
import { Source } from './entry-root';
/** Split entry ID (e.g. 'emd-1832') into source ('emdb') and number ('1832') */
export function splitEntryId(entryId: string) {
const PREFIX_TO_SOURCE: { [prefix: string]: Source } = { 'emd': 'emdb' };
const [prefix, entry] = entryId.split('-');
return {
source: PREFIX_TO_SOURCE[prefix] ?? prefix,
entryNumber: entry
};
}
/** Create entry ID (e.g. 'emd-1832') for a combination of source ('emdb') and number ('1832') */
export function createEntryId(source: Source, entryNumber: string | number) {
const SOURCE_TO_PREFIX: { [prefix: string]: string } = { 'emdb': 'emd' };
const prefix = SOURCE_TO_PREFIX[source] ?? source;
return `${prefix}-${entryNumber}`;
}
/**
* Represents a set of values to choose from, with a default value. Example:
* ```
* export const MyChoice = new Choice({ yes: 'I agree', no: 'Nope' }, 'yes');
* export type MyChoiceType = Choice.Values<typeof MyChoice>; // 'yes'|'no'
* ```
*/
export class Choice<T extends string, D extends T> {
readonly defaultValue: D;
readonly options: [T, string][];
private readonly nameDict: { [value in T]: string };
constructor(opts: { [value in T]: string }, defaultValue: D) {
this.defaultValue = defaultValue;
this.options = Object.keys(opts).map(k => [k as T, opts[k as T]]);
this.nameDict = opts;
}
PDSelect(defaultValue?: T, info?: ParamDefinition.Info): ParamDefinition.Select<T> {
return ParamDefinition.Select<T>(defaultValue ?? this.defaultValue, this.options, info);
}
prettyName(value: T): string {
return this.nameDict[value];
}
get values(): T[] {
return this.options.map(([value, pretty]) => value);
}
}
export namespace Choice {
export type Values<T extends Choice<any, any>> = T extends Choice<infer R, any> ? R : any;
}
export function isDefined<T>(x: T | undefined): x is T {
return x !== undefined;
}
export class NodeManager {
private nodes: { [key: string]: StateObjectSelector };
constructor() {
this.nodes = {};
}
private static nodeExists(node: StateObjectSelector): boolean {
try {
return node.checkValid();
} catch {
return false;
}
}
public getNode(key: string): StateObjectSelector | undefined {
const node = this.nodes[key];
if (node && !NodeManager.nodeExists(node)) {
delete this.nodes[key];
return undefined;
}
return node;
}
public getNodes(): StateObjectSelector[] {
return Object.keys(this.nodes).map(key => this.getNode(key)).filter(node => node) as StateObjectSelector[];
}
public deleteAllNodes(update: StateBuilder.Root) {
for (const node of this.getNodes()) {
update.delete(node);
}
this.nodes = {};
}
public hideAllNodes() {
for (const node of this.getNodes()) {
setSubtreeVisibility(node.state!, node.ref, true); // hide
}
}
public async showNode(key: string, factory: () => StateObjectSelector | Promise<StateObjectSelector>, forceVisible: boolean = true) {
let node = this.getNode(key);
if (node) {
if (forceVisible) {
setSubtreeVisibility(node.state!, node.ref, false); // show
}
} else {
node = await factory();
this.nodes[key] = node;
}
return node;
}
}
const CreateTransformer = StateTransformer.builderFactory('volseg');
export const CreateVolume = CreateTransformer({
name: 'create-transformer',
from: PluginStateObject.Root,
to: PluginStateObject.Volume.Data,
params: {
label: ParamDefinition.Text('Volume', { isHidden: true }),
description: ParamDefinition.Text('', { isHidden: true }),
volume: ParamDefinition.Value<Volume>(undefined as any, { isHidden: true }),
}
})({
apply({ params }) {
return new PluginStateObject.Volume.Data(params.volume, { label: params.label, description: params.description });
}
});
export function applyEllipsis(name: string, max_chars: number = 60) {
if (name.length <= max_chars) return name;
const beginning = name.substring(0, max_chars);
let lastSpace = beginning.lastIndexOf(' ');
if (lastSpace === -1) return beginning + '...';
if (lastSpace > 0 && ',;.'.includes(name.charAt(lastSpace - 1))) lastSpace--;
return name.substring(0, lastSpace) + '...';
}
export function lazyGetter<T>(getter: () => T, errorIfUndefined?: string) {
let value: T | undefined = undefined;
return () => {
if (value === undefined) value = getter();
if (errorIfUndefined && value === undefined) throw new Error(errorIfUndefined);
return value;
};
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject as SO } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginConfigItem } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { StateAction } from '../../mol-state';
import { Task } from '../../mol-task';
import { DEFAULT_VOLSEG_SERVER, VolumeApiV2 } from './volseg-api/api';
import { VolsegEntryData, VolsegEntryParamValues, createLoadVolsegParams } from './entry-root';
import { VolsegGlobalState } from './global-state';
import { createEntryId } from './helpers';
import { VolsegEntryFromRoot, VolsegGlobalStateFromRoot, VolsegStateFromEntry } from './transformers';
import { VolsegUI } from './ui';
const DEBUGGING = window.location.hostname === 'localhost';
export const VolsegVolumeServerConfig = {
// DefaultServer: new PluginConfigItem('volseg-volume-server', DEFAULT_VOLUME_SERVER_V2),
DefaultServer: new PluginConfigItem('volseg-volume-server', DEBUGGING ? 'http://localhost:9000/v2' : DEFAULT_VOLSEG_SERVER),
};
export const Volseg = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
name: 'volseg',
category: 'misc',
display: {
name: 'Volseg',
description: 'Volseg'
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
register() {
this.ctx.state.data.actions.add(LoadVolseg);
this.ctx.customStructureControls.set('volseg', VolsegUI as any);
this.initializeEntryLists(); // do not await
const entries = new Map<string, VolsegEntryData>();
this.subscribeObservable(this.ctx.state.data.events.cell.created, o => {
if (o.cell.obj instanceof VolsegEntryData) entries.set(o.ref, o.cell.obj);
});
this.subscribeObservable(this.ctx.state.data.events.cell.removed, o => {
if (entries.has(o.ref)) {
entries.get(o.ref)!.dispose();
entries.delete(o.ref);
}
});
}
unregister() {
this.ctx.state.data.actions.remove(LoadVolseg);
this.ctx.customStructureControls.delete('volseg');
}
private async initializeEntryLists() {
const apiUrl = this.ctx.config.get(VolsegVolumeServerConfig.DefaultServer) ?? DEFAULT_VOLSEG_SERVER;
const api = new VolumeApiV2(apiUrl);
const entryLists = await api.getEntryList(10 ** 6);
Object.values(entryLists).forEach(l => l.sort());
(this.ctx.customState as any).volsegAvailableEntries = entryLists;
}
}
});
export const LoadVolseg = StateAction.build({
display: { name: 'Load Volume & Segmentation' },
from: SO.Root,
params: (a, plugin: PluginContext) => {
const res = createLoadVolsegParams(plugin, (plugin.customState as any).volsegAvailableEntries);
return res;
},
})(({ params, state }, ctx: PluginContext) => Task.create('Loading Volume & Segmentation', taskCtx => {
return state.transaction(async () => {
const entryParams = VolsegEntryParamValues.fromLoadVolsegParamValues(params);
if (entryParams.entryId.trim().length === 0) {
alert('Must specify Entry Id!');
throw new Error('Specify Entry Id');
}
if (!entryParams.entryId.includes('-')) {
// add source prefix if the user omitted it (e.g. 1832 -> emd-1832)
entryParams.entryId = createEntryId(entryParams.source, entryParams.entryId);
}
ctx.behaviors.layout.leftPanelTabName.next('data');
const globalStateNode = ctx.state.data.selectQ(q => q.ofType(VolsegGlobalState))[0];
if (!globalStateNode) {
await state.build().toRoot().apply(VolsegGlobalStateFromRoot, {}, { state: { isGhost: !DEBUGGING } }).commit();
}
const entryNode = await state.build().toRoot().apply(VolsegEntryFromRoot, entryParams).commit();
await state.build().to(entryNode).apply(VolsegStateFromEntry, {}, { state: { isGhost: !DEBUGGING } }).commit();
if (entryNode.data) {
await entryNode.data.loadVolume();
await entryNode.data.loadSegmentations();
}
}).runInContext(taskCtx);
}));

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
import { PluginContext } from '../../mol-plugin/context';
import { StateTransformer } from '../../mol-state';
import { Task } from '../../mol-task';
import { VolsegEntry, VolsegEntryData, createVolsegEntryParams } from './entry-root';
import { VolsegState, VolsegStateParams, VOLSEG_STATE_FROM_ENTRY_TRANSFORMER_NAME } from './entry-state';
import { VolsegGlobalState, VolsegGlobalStateData, VolsegGlobalStateParams } from './global-state';
export const VolsegEntryFromRoot = PluginStateTransform.BuiltIn({
name: 'volseg-entry-from-root',
display: { name: 'Vol & Seg Entry', description: 'Vol & Seg Entry' },
from: PluginStateObject.Root,
to: VolsegEntry,
params: (a, plugin: PluginContext) => createVolsegEntryParams(plugin),
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Load Vol & Seg Entry', async () => {
const data = await VolsegEntryData.create(plugin, params);
return new VolsegEntry(data, { label: data.entryId, description: 'Vol & Seg Entry' });
});
},
update({ b, oldParams, newParams }) {
Object.assign(newParams, oldParams);
console.error('Changing params of existing VolsegEntry node is not allowed');
return StateTransformer.UpdateResult.Unchanged;
}
});
export const VolsegStateFromEntry = PluginStateTransform.BuiltIn({
name: VOLSEG_STATE_FROM_ENTRY_TRANSFORMER_NAME,
display: { name: 'Vol & Seg Entry State', description: 'Vol & Seg Entry State' },
from: VolsegEntry,
to: VolsegState,
params: VolsegStateParams,
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Create Vol & Seg Entry State', async () => {
return new VolsegState(params, { label: 'State' });
});
}
});
export const VolsegGlobalStateFromRoot = PluginStateTransform.BuiltIn({
name: 'volseg-global-state-from-root',
display: { name: 'Vol & Seg Global State', description: 'Vol & Seg Global State' },
from: PluginStateObject.Root,
to: VolsegGlobalState,
params: VolsegGlobalStateParams,
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Create Vol & Seg Global State', async () => {
const data = new VolsegGlobalStateData(plugin, params);
return new VolsegGlobalState(data, { label: 'Global State', description: 'Vol & Seg Global State' });
});
},
update({ b, oldParams, newParams }) {
b.data.currentState.next(newParams);
return StateTransformer.UpdateResult.Updated;
}
});

View File

@@ -0,0 +1,264 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { useCallback, useEffect, useRef, useState } from 'react';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button, ControlRow, ExpandGroup, IconButton } from '../../mol-plugin-ui/controls/common';
import * as Icons from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { Slider } from '../../mol-plugin-ui/controls/slider';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { UpdateTransformControl } from '../../mol-plugin-ui/state/update-transform';
import { PluginContext } from '../../mol-plugin/context';
import { shallowEqualArrays } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { sleep } from '../../mol-util/sleep';
import { VolsegEntry, VolsegEntryData } from './entry-root';
import { SimpleVolumeParams, SimpleVolumeParamValues } from './entry-volume';
import { VolsegGlobalState, VolsegGlobalStateData, VolsegGlobalStateParams } from './global-state';
import { isDefined } from './helpers';
interface VolsegUIData {
globalState?: VolsegGlobalStateData,
availableNodes: VolsegEntry[],
activeNode?: VolsegEntry,
}
namespace VolsegUIData {
export function changeAvailableNodes(data: VolsegUIData, newNodes: VolsegEntry[]): VolsegUIData {
const newActiveNode = newNodes.length > data.availableNodes.length ?
newNodes[newNodes.length - 1]
: newNodes.find(node => node.data.ref === data.activeNode?.data.ref) ?? newNodes[0];
return { ...data, availableNodes: newNodes, activeNode: newActiveNode };
}
export function changeActiveNode(data: VolsegUIData, newActiveRef: string): VolsegUIData {
const newActiveNode = data.availableNodes.find(node => node.data.ref === newActiveRef) ?? data.availableNodes[0];
return { ...data, availableNodes: data.availableNodes, activeNode: newActiveNode };
}
export function equals(data1: VolsegUIData, data2: VolsegUIData) {
return shallowEqualArrays(data1.availableNodes, data2.availableNodes) && data1.activeNode === data2.activeNode && data1.globalState === data2.globalState;
}
}
export class VolsegUI extends CollapsableControls<{}, { data: VolsegUIData }> {
protected defaultState(): CollapsableState & { data: VolsegUIData } {
return {
header: 'Volume & Segmentation',
isCollapsed: true,
brand: { accent: 'orange', svg: Icons.ExtensionSvg },
data: {
globalState: undefined,
availableNodes: [],
activeNode: undefined,
}
};
}
protected renderControls(): JSX.Element | null {
return <VolsegControls plugin={this.plugin} data={this.state.data} setData={d => this.setState({ data: d })} />;
}
componentDidMount(): void {
this.setState({ isHidden: true, isCollapsed: false });
this.subscribe(this.plugin.state.data.events.changed, e => {
const nodes = e.state.selectQ(q => q.ofType(VolsegEntry)).map(cell => cell?.obj).filter(isDefined);
const isHidden = nodes.length === 0;
const newData = VolsegUIData.changeAvailableNodes(this.state.data, nodes);
if (!this.state.data.globalState?.isRegistered()) {
const globalState = e.state.selectQ(q => q.ofType(VolsegGlobalState))[0]?.obj?.data;
if (globalState) newData.globalState = globalState;
}
if (!VolsegUIData.equals(this.state.data, newData) || this.state.isHidden !== isHidden) {
this.setState({ data: newData, isHidden: isHidden });
}
});
}
}
function VolsegControls({ plugin, data, setData }: { plugin: PluginContext, data: VolsegUIData, setData: (d: VolsegUIData) => void }) {
const entryData = data.activeNode?.data;
if (!entryData) {
return <p>No data!</p>;
}
if (!data.globalState) {
return <p>No global state!</p>;
}
const params = {
/** Reference to the active VolsegEntry node */
entry: PD.Select(data.activeNode!.data.ref, data.availableNodes.map(entry => [entry.data.ref, entry.data.entryId]))
};
const values: PD.Values<typeof params> = {
entry: data.activeNode!.data.ref,
};
const globalState = useBehavior(data.globalState.currentState);
return <>
<ParameterControls params={params} values={values} onChangeValues={next => setData(VolsegUIData.changeActiveNode(data, next.entry))} />
<ExpandGroup header='Global options'>
<WaitingParameterControls params={VolsegGlobalStateParams} values={globalState} onChangeValues={async next => await data.globalState?.updateState(plugin, next)} />
</ExpandGroup>
<VolsegEntryControls entryData={entryData} key={entryData.ref} />
</>;
}
function VolsegEntryControls({ entryData }: { entryData: VolsegEntryData }) {
const state = useBehavior(entryData.currentState);
const allSegments = entryData.metadata.allSegments;
const selectedSegment = entryData.metadata.getSegment(state.selectedSegment);
const visibleSegments = state.visibleSegments.map(seg => seg.segmentId);
const visibleModels = state.visibleModels.map(model => model.pdbId);
const allPdbs = entryData.pdbs;
return <>
{/* Title */}
<div style={{ fontWeight: 'bold', padding: 8, paddingTop: 6, paddingBottom: 4, overflow: 'hidden' }}>
{entryData.metadata.raw.annotation?.name ?? 'Unnamed Annotation'}
</div>
{/* Fitted models */}
{allPdbs.length > 0 && <ExpandGroup header='Fitted models in PDB' initiallyExpanded>
{allPdbs.map(pdb =>
<WaitingButton key={pdb} onClick={() => entryData.actionShowFittedModel(visibleModels.includes(pdb) ? [] : [pdb])}
style={{ fontWeight: visibleModels.includes(pdb) ? 'bold' : undefined, textAlign: 'left', marginTop: 1 }}>
{pdb}
</WaitingButton>
)}
</ExpandGroup>}
{/* Volume */}
<VolumeControls entryData={entryData} />
<ExpandGroup header='Segmentation data' initiallyExpanded>
{/* Segment opacity slider */}
<ControlRow label='Opacity' control={
<WaitingSlider min={0} max={1} value={state.segmentOpacity} step={0.05} onChange={async v => await entryData.actionSetOpacity(v)} />
} />
{/* Segment toggles */}
{allSegments.length > 0 && <>
<WaitingButton onClick={async () => { await sleep(20); await entryData.actionToggleAllSegments(); }} style={{ marginTop: 1 }}>
Toggle All segments
</WaitingButton>
<div style={{ maxHeight: 200, overflow: 'hidden', overflowY: 'auto', marginBlock: 1 }}>
{allSegments.map(segment =>
<div style={{ display: 'flex', marginBottom: 1 }} key={segment.id}
onMouseEnter={() => entryData.actionHighlightSegment(segment)}
onMouseLeave={() => entryData.actionHighlightSegment()}>
<Button onClick={() => entryData.actionSelectSegment(segment !== selectedSegment ? segment.id : undefined)}
style={{ fontWeight: segment.id === selectedSegment?.id ? 'bold' : undefined, marginRight: 1, flexGrow: 1, textAlign: 'left' }}>
<div title={segment.biological_annotation.name ?? 'Unnamed segment'} style={{ maxWidth: 240, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{segment.biological_annotation.name ?? 'Unnamed segment'} ({segment.id})
</div>
</Button>
<IconButton svg={visibleSegments.includes(segment.id) ? Icons.VisibilityOutlinedSvg : Icons.VisibilityOffOutlinedSvg}
onClick={() => entryData.actionToggleSegment(segment.id)} />
</div>
)}
</div>
</>}
</ExpandGroup>
{/* Segment annotations */}
<ExpandGroup header='Selected segment annotation' initiallyExpanded>
<div style={{ paddingTop: 4, paddingRight: 8, maxHeight: 300, overflow: 'hidden', overflowY: 'auto' }}>
{!selectedSegment && 'No segment selected'}
{selectedSegment && <b>Segment {selectedSegment.id}:<br />{selectedSegment.biological_annotation.name ?? 'Unnamed segment'}</b>}
{selectedSegment?.biological_annotation.external_references.map(ref =>
<p key={ref.id} style={{ marginTop: 4 }}>
<small>{ref.resource}:{ref.accession}</small><br />
<b>{capitalize(ref.label)}</b><br />
{ref.description}
</p>)}
</div>
</ExpandGroup>
</>;
}
function VolumeControls({ entryData }: { entryData: VolsegEntryData }) {
const vol = useBehavior(entryData.currentVolume);
if (!vol) return null;
const volumeValues: SimpleVolumeParamValues = {
volumeType: vol.state.isHidden ? 'off' : vol.params?.type.name as any,
opacity: vol.params?.type.params.alpha,
};
return <ExpandGroup header='Volume data' initiallyExpanded>
<WaitingParameterControls params={SimpleVolumeParams} values={volumeValues} onChangeValues={async next => { await sleep(20); await entryData.actionUpdateVolumeVisual(next); }} />
<ExpandGroup header='Detailed Volume Params' headerStyle={{ marginTop: 1 }}>
<UpdateTransformControl state={entryData.plugin.state.data} transform={vol} customHeader='none' />
</ExpandGroup>
</ExpandGroup>;
}
type ComponentParams<T extends React.Component<any, any, any> | ((props: any) => JSX.Element)> =
T extends React.Component<infer P, any, any> ? P : T extends (props: infer P) => JSX.Element ? P : never;
function WaitingSlider({ value, onChange, ...etc }: { value: number, onChange: (value: number) => any } & ComponentParams<Slider>) {
const [changing, sliderValue, execute] = useAsyncChange(value);
return <Slider value={sliderValue} disabled={changing} onChange={newValue => execute(onChange, newValue)} {...etc} />;
}
function WaitingButton({ onClick, ...etc }: { onClick: () => any } & ComponentParams<typeof Button>) {
const [changing, _, execute] = useAsyncChange(undefined);
return <Button disabled={changing} onClick={() => execute(onClick, undefined)} {...etc}>
{etc.children}
</Button>;
}
function WaitingParameterControls<T extends PD.Params>({ values, onChangeValues, ...etc }: { values: PD.ValuesFor<T>, onChangeValues: (values: PD.ValuesFor<T>) => any } & ComponentParams<ParameterControls<T>>) {
const [changing, currentValues, execute] = useAsyncChange(values);
return <ParameterControls isDisabled={changing} values={currentValues} onChangeValues={newValue => execute(onChangeValues, newValue)} {...etc} />;
}
function capitalize(text: string) {
const first = text.charAt(0);
const rest = text.slice(1);
return first.toUpperCase() + rest;
}
function useAsyncChange<T>(initialValue: T) {
const [isExecuting, setIsExecuting] = useState(false);
const [value, setValue] = useState(initialValue);
const isMounted = useRef(false);
useEffect(() => setValue(initialValue), [initialValue]);
useEffect(() => {
isMounted.current = true;
return () => { isMounted.current = false; };
}, []);
const execute = useCallback(
async (func: (val: T) => Promise<any>, val: T) => {
setIsExecuting(true);
setValue(val);
try {
await func(val);
} catch (err) {
if (isMounted.current) {
setValue(initialValue);
}
throw err;
} finally {
if (isMounted.current) {
setIsExecuting(false);
}
}
},
[]
);
return [isExecuting, value, execute] as const;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { type Metadata } from './data';
export const DEFAULT_VOLSEG_SERVER = 'https://molstarvolseg.ncbr.muni.cz/v2';
export class VolumeApiV2 {
public volumeServerUrl: string;
public constructor(volumeServerUrl: string = DEFAULT_VOLSEG_SERVER) {
this.volumeServerUrl = volumeServerUrl.replace(/\/$/, ''); // trim trailing slash
}
public entryListUrl(maxEntries: number, keyword?: string): string {
return `${this.volumeServerUrl}/list_entries/${maxEntries}/${keyword ?? ''}`;
}
public metadataUrl(source: string, entryId: string): string {
return `${this.volumeServerUrl}/${source}/${entryId}/metadata`;
}
public volumeUrl(source: string, entryId: string, box: [[number, number, number], [number, number, number]] | null, maxPoints: number): string {
if (box) {
const [[a1, a2, a3], [b1, b2, b3]] = box;
return `${this.volumeServerUrl}/${source}/${entryId}/volume/box/${a1}/${a2}/${a3}/${b1}/${b2}/${b3}?max_points=${maxPoints}`;
} else {
return `${this.volumeServerUrl}/${source}/${entryId}/volume/cell?max_points=${maxPoints}`;
}
}
public latticeUrl(source: string, entryId: string, segmentation: number, box: [[number, number, number], [number, number, number]] | null, maxPoints: number): string {
if (box) {
const [[a1, a2, a3], [b1, b2, b3]] = box;
return `${this.volumeServerUrl}/${source}/${entryId}/segmentation/box/${segmentation}/${a1}/${a2}/${a3}/${b1}/${b2}/${b3}?max_points=${maxPoints}`;
} else {
return `${this.volumeServerUrl}/${source}/${entryId}/segmentation/cell/${segmentation}?max_points=${maxPoints}`;
}
}
public meshUrl_Json(source: string, entryId: string, segment: number, detailLevel: number): string {
return `${this.volumeServerUrl}/${source}/${entryId}/mesh/${segment}/${detailLevel}`;
}
public meshUrl_Bcif(source: string, entryId: string, segment: number, detailLevel: number): string {
return `${this.volumeServerUrl}/${source}/${entryId}/mesh_bcif/${segment}/${detailLevel}`;
}
public volumeInfoUrl(source: string, entryId: string): string {
return `${this.volumeServerUrl}/${source}/${entryId}/volume_info`;
}
public async getEntryList(maxEntries: number, keyword?: string): Promise<{ [source: string]: string[] }> {
const response = await fetch(this.entryListUrl(maxEntries, keyword));
return await response.json();
}
public async getMetadata(source: string, entryId: string): Promise<Metadata> {
const url = this.metadataUrl(source, entryId);
const response = await fetch(url);
if (!response.ok) throw new Error(`Failed to fetch metadata from ${url}`);
return await response.json();
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
export interface Metadata {
grid: {
general: {
details: string,
source_db_name: string,
source_db_id: string,
},
volumes: Volumes,
segmentation_lattices: SegmentationLattices,
segmentation_meshes: SegmentationMeshes,
},
annotation: Annotation | null,
}
export interface Volumes {
volume_downsamplings: number[],
voxel_size: { [downsampling: number]: Vector3 },
origin: Vector3,
grid_dimensions: Vector3,
sampled_grid_dimensions: { [downsampling: number]: Vector3 },
mean: { [downsampling: number]: number },
std: { [downsampling: number]: number },
min: { [downsampling: number]: number },
max: { [downsampling: number]: number },
volume_force_dtype: string,
}
export interface SegmentationLattices {
segmentation_lattice_ids: number[],
segmentation_downsamplings: { [lattice: number]: number[] },
}
export interface SegmentationMeshes {
mesh_component_numbers: {
segment_ids?: {
[segId: number]: {
detail_lvls: {
[detail: number]: {
mesh_ids: {
[meshId: number]: {
num_triangles: number,
num_vertices: number
}
}
}
}
}
}
}
detail_lvl_to_fraction: {
[lvl: number]: number
}
}
export interface Annotation {
name: string,
details: string,
segment_list: Segment[],
}
export interface Segment {
id: number,
colour: number[],
biological_annotation: BiologicalAnnotation,
}
export interface BiologicalAnnotation {
name: string,
external_references: ExternalReference[]
}
export interface ExternalReference {
id: number, resource: string, accession: string, label: string,
description: string
}
type Vector3 = [number, number, number];

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Metadata, Segment } from './data';
export class MetadataWrapper {
raw: Metadata;
private segmentMap?: { [id: number]: Segment };
constructor(rawMetadata: Metadata) {
this.raw = rawMetadata;
}
get allSegments() {
return this.raw.annotation?.segment_list ?? [];
}
get allSegmentIds() {
return this.allSegments.map(segment => segment.id);
}
getSegment(segmentId: number): Segment | undefined {
if (!this.segmentMap) {
this.segmentMap = {};
for (const segment of this.allSegments) {
this.segmentMap[segment.id] = segment;
}
}
return this.segmentMap[segmentId];
}
/** Get the list of detail levels available for the given mesh segment. */
getMeshDetailLevels(segmentId: number): number[] {
const segmentIds = this.raw.grid.segmentation_meshes.mesh_component_numbers.segment_ids;
if (!segmentIds) return [];
const details = segmentIds[segmentId].detail_lvls;
return Object.keys(details).map(s => parseInt(s));
}
/** Get the worst available detail level that is not worse than preferredDetail.
* If preferredDetail is null, get the worst detail level overall.
* (worse = greater number) */
getSufficientMeshDetail(segmentId: number, preferredDetail: number | null) {
let availDetails = this.getMeshDetailLevels(segmentId);
if (preferredDetail !== null) {
availDetails = availDetails.filter(det => det <= preferredDetail);
}
return Math.max(...availDetails);
}
/** IDs of all segments available as meshes */
get meshSegmentIds() {
const segmentIds = this.raw.grid.segmentation_meshes.mesh_component_numbers.segment_ids;
if (!segmentIds) return [];
return Object.keys(segmentIds).map(s => parseInt(s));
}
get gridTotalVolume() {
const [vx, vy, vz] = this.raw.grid.volumes.voxel_size[1];
const [gx, gy, gz] = this.raw.grid.volumes.grid_dimensions;
return vx * vy * vz * gx * gy * gz;
}
}

View File

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

View File

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

View File

@@ -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,8 @@ interface Canvas3DContext {
namespace Canvas3DContext {
export const DefaultAttribs = {
powerPreference: 'high-performance' as WebGLContextAttributes['powerPreference'],
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 +131,20 @@ 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 { powerPreference, failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const gl = getGLContext(canvas, {
powerPreference,
failIfMajorPerformanceCaveat,
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
@@ -285,7 +297,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 +314,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 +325,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 +691,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 +818,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 +833,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 +875,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 +940,4 @@ namespace Canvas3D {
Viewport.set(controls.viewport, x, y, width, height);
}
}
}
}

View File

@@ -49,6 +49,7 @@ const SkyboxParams = {
pz: PD.File({ label: 'Positive Z / Front', accept: 'image/*' }),
}, { isExpanded: true, label: 'Files' }),
}),
blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or when "EXT_shader_texture_lod" is available.' }),
...SharedParams,
};
type SkyboxProps = PD.Values<typeof SkyboxParams>
@@ -170,6 +171,7 @@ export class BackgroundPass {
Mat4.invert(m, m);
ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m);
ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur);
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness);
@@ -367,7 +369,7 @@ function getSkyboxTexture(ctx: WebGLContext, assetManager: AssetManager, faces:
const cubeAssets = getCubeAssets(assetManager, faces);
const cubeFaces = getCubeFaces(assetManager, cubeAssets);
const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz];
const texture = ctx.resources.cubeTexture(cubeFaces, false, onload);
const texture = ctx.resources.cubeTexture(cubeFaces, true, onload);
return { texture, assets };
}
@@ -424,12 +426,15 @@ const BackgroundSchema = {
uGradientColorA: UniformSpec('v3'),
uGradientColorB: UniformSpec('v3'),
uGradientRatio: UniformSpec('f'),
uBlur: UniformSpec('f'),
uOpacity: UniformSpec('f'),
uSaturation: UniformSpec('f'),
uLightness: UniformSpec('f'),
dVariant: DefineSpec('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']),
};
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag);
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag, {
shaderTextureLod: 'optional'
});
type BackgroundRenderable = ComputeRenderable<Values<typeof BackgroundSchema>>
function getBackgroundRenderable(ctx: WebGLContext, width: number, height: number): BackgroundRenderable {
@@ -448,6 +453,7 @@ function getBackgroundRenderable(ctx: WebGLContext, width: number, height: numbe
uGradientColorA: ValueCell.create(Vec3()),
uGradientColorB: ValueCell.create(Vec3()),
uGradientRatio: ValueCell.create(0.5),
uBlur: ValueCell.create(0),
uOpacity: ValueCell.create(1),
uSaturation: ValueCell.create(0),
uLightness: ValueCell.create(0),

View 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();
}
}

View File

@@ -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.isTransparentOutlineEnabled(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, renderer.light);
}
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');
@@ -129,7 +196,7 @@ export class DrawPass {
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
@@ -137,7 +204,7 @@ export class DrawPass {
}
}
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
}
// render transparent primitives and volumes
@@ -193,7 +260,7 @@ export class DrawPass {
this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
}
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
@@ -201,7 +268,7 @@ export class DrawPass {
}
}
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
@@ -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;
}
}
}

View File

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

View File

@@ -61,6 +61,7 @@ type Props = {
postprocessing: PostprocessingProps
marking: MarkingProps
transparentBackground: boolean;
dpoitIterations: number;
}
type RenderContext = {

View File

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

View File

@@ -3,6 +3,7 @@
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
@@ -30,6 +31,8 @@ import { SmaaParams, SmaaPass } from './smaa';
import { isTimingMode } from '../../mol-util/debug';
import { BackgroundParams, BackgroundPass } from './background';
import { AssetManager } from '../../mol-util/assets';
import { Light } from '../../mol-gl/renderer';
import { shadows_frag } from '../../mol-gl/shader/shadows.frag';
const OutlinesSchema = {
...QuadSchema,
@@ -42,10 +45,12 @@ const OutlinesSchema = {
uFar: UniformSpec('f'),
uMaxPossibleViewZDiff: UniformSpec('f'),
dTransparentOutline: DefineSpec('boolean'),
};
type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture): OutlinesRenderable {
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
const width = depthTextureOpaque.getWidth();
const height = depthTextureOpaque.getHeight();
@@ -60,6 +65,8 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
uFar: ValueCell.create(10000),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
dTransparentOutline: ValueCell.create(transparentOutline),
};
const schema = { ...OutlinesSchema };
@@ -69,6 +76,64 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
return createComputeRenderable(renderItem, values);
}
const ShadowsSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
dSteps: DefineSpec('number'),
uMaxDistance: UniformSpec('f'),
uTolerance: UniformSpec('f'),
uBias: UniformSpec('f'),
uLightDirection: UniformSpec('v3[]'),
uLightColor: UniformSpec('v3[]'),
dLightCount: DefineSpec('number'),
};
type ShadowsRenderable = ComputeRenderable<Values<typeof ShadowsSchema>>
function getShadowsRenderable(ctx: WebGLContext, depthTexture: Texture): ShadowsRenderable {
const width = depthTexture.getWidth();
const height = depthTexture.getHeight();
const values: Values<typeof ShadowsSchema> = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uTexSize: ValueCell.create(Vec2.create(width, height)),
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
dSteps: ValueCell.create(1),
uMaxDistance: ValueCell.create(3.0),
uTolerance: ValueCell.create(1.0),
uBias: ValueCell.create(0.6),
uLightDirection: ValueCell.create([]),
uLightColor: ValueCell.create([]),
dLightCount: ValueCell.create(0),
};
const schema = { ...ShadowsSchema };
const shaderCode = ShaderCode('shadows', quad_vert, shadows_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
@@ -204,6 +269,7 @@ const PostprocessingSchema = {
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tShadows: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
@@ -221,19 +287,25 @@ const PostprocessingSchema = {
dOcclusionEnable: DefineSpec('boolean'),
uOcclusionOffset: UniformSpec('v2'),
dShadowEnable: DefineSpec('boolean'),
dOutlineEnable: DefineSpec('boolean'),
dOutlineScale: DefineSpec('number'),
uOutlineThreshold: UniformSpec('f'),
dTransparentOutline: DefineSpec('boolean'),
};
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture, transparentOutline: boolean): PostprocessingRenderable {
const values: Values<typeof PostprocessingSchema> = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
tColor: ValueCell.create(colorTexture),
tDepthOpaque: ValueCell.create(depthTextureOpaque),
tDepthTransparent: ValueCell.create(depthTextureTransparent),
tShadows: ValueCell.create(shadowsTexture),
tOutlines: ValueCell.create(outlinesTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
@@ -251,9 +323,13 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
dOcclusionEnable: ValueCell.create(true),
uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
dShadowEnable: ValueCell.create(false),
dOutlineEnable: ValueCell.create(false),
dOutlineScale: ValueCell.create(1),
uOutlineThreshold: ValueCell.create(0.33),
dTransparentOutline: ValueCell.create(transparentOutline),
};
const schema = { ...PostprocessingSchema };
@@ -274,11 +350,21 @@ export const PostprocessingParams = {
}),
off: PD.Group({})
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
shadow: PD.MappedStatic('off', {
on: PD.Group({
steps: PD.Numeric(1, { min: 1, max: 64, step: 1 }),
bias: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
maxDistance: PD.Numeric(3, { min: 0, max: 256, step: 1 }),
tolerance: PD.Numeric(1.0, { min: 0.0, max: 10.0, step: 0.1 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Simplistic shadows' }),
outline: PD.MappedStatic('off', {
on: PD.Group({
scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
color: PD.Color(Color(0x000000)),
includeTransparent: PD.Boolean(true, { description: 'Whether to show outline for transparent objects' }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Draw outline around 3D objects' }),
@@ -289,15 +375,16 @@ export const PostprocessingParams = {
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
background: PD.Group(BackgroundParams, { isFlat: true }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
export class PostprocessingPass {
static isEnabled(props: PostprocessingProps) {
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
return props.occlusion.name === 'on' || props.shadow.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
}
static isOutlineEnabled(props: PostprocessingProps) {
return props.outline.name === 'on';
static isTransparentOutlineEnabled(props: PostprocessingProps) {
return props.outline.name === 'on' && props.outline.params.includeTransparent;
}
readonly target: RenderTarget;
@@ -305,6 +392,9 @@ export class PostprocessingPass {
private readonly outlinesTarget: RenderTarget;
private readonly outlinesRenderable: OutlinesRenderable;
private readonly shadowsTarget: RenderTarget;
private readonly shadowsRenderable: ShadowsRenderable;
private readonly ssaoFramebuffer: Framebuffer;
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
@@ -348,7 +438,10 @@ export class PostprocessingPass {
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent, true);
this.shadowsTarget = webgl.createRenderTarget(width, height, false);
this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
this.ssaoFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
@@ -373,7 +466,7 @@ export class PostprocessingPass {
this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture);
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
this.background = new BackgroundPass(webgl, assetManager, width, height);
}
@@ -389,12 +482,14 @@ export class PostprocessingPass {
const sh = Math.floor(height * this.ssaoScale);
this.target.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.shadowsTarget.setSize(width, height);
this.downsampledDepthTarget.setSize(sw, sh);
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.define(sw, sh);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.shadowsRenderable.values.uTexSize, Vec2.set(this.shadowsRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
@@ -404,25 +499,29 @@ export class PostprocessingPass {
}
}
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
let needsUpdateShadows = false;
let needsUpdateMain = false;
let needsUpdateSsao = false;
let needsUpdateSsaoBlur = false;
let needsUpdateOutlines = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
const outlinesEnabled = props.outline.name === 'on';
const shadowsEnabled = props.shadow.name === 'on';
const occlusionEnabled = props.occlusion.name === 'on';
const invProjection = Mat4.identity();
Mat4.invert(invProjection, camera.projection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const v = camera.viewport;
if (props.occlusion.name === 'on') {
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const b = this.ssaoRenderable.values.uBounds;
const v = camera.viewport;
const s = this.ssaoScale;
Vec4.set(b.ref.value,
Math.floor(v.x * s) / (w * s),
@@ -494,8 +593,41 @@ export class PostprocessingPass {
}
}
if (props.shadow.name === 'on') {
ValueCell.update(this.shadowsRenderable.values.uProjection, camera.projection);
ValueCell.update(this.shadowsRenderable.values.uInvProjection, invProjection);
Vec4.set(this.shadowsRenderable.values.uBounds.ref.value,
v.x / w,
v.y / h,
(v.x + v.width) / w,
(v.y + v.height) / h
);
ValueCell.update(this.shadowsRenderable.values.uBounds, this.shadowsRenderable.values.uBounds.ref.value);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.shadowsRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uMaxDistance, props.shadow.params.maxDistance);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uTolerance, props.shadow.params.tolerance);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uBias, props.shadow.params.bias);
if (this.shadowsRenderable.values.dSteps.ref.value !== props.shadow.params.steps) {
ValueCell.update(this.shadowsRenderable.values.dSteps, props.shadow.params.steps);
needsUpdateShadows = true;
}
ValueCell.update(this.shadowsRenderable.values.uLightDirection, light.direction);
ValueCell.update(this.shadowsRenderable.values.uLightColor, light.color);
if (this.shadowsRenderable.values.dLightCount.ref.value !== light.count) {
ValueCell.update(this.shadowsRenderable.values.dLightCount, light.count);
needsUpdateShadows = true;
}
}
if (props.outline.name === 'on') {
let { threshold } = props.outline.params;
let { threshold, includeTransparent } = props.outline.params;
const transparentOutline = includeTransparent ?? true;
// orthographic needs lower threshold
if (camera.state.mode === 'orthographic') threshold /= 5;
const factor = Math.pow(1000, threshold) / 1000;
@@ -506,12 +638,16 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateOutlines = true; }
ValueCell.updateIfChanged(this.outlinesRenderable.values.dTransparentOutline, transparentOutline);
ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dTransparentOutline, transparentOutline);
}
ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
@@ -522,11 +658,22 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic);
if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled);
if (this.renderable.values.dShadowEnable.ref.value !== shadowsEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dShadowEnable, shadowsEnabled);
if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
if (needsUpdateOutlines) {
this.outlinesRenderable.update();
}
if (needsUpdateShadows) {
this.shadowsRenderable.update();
}
if (needsUpdateSsao) {
this.ssaoRenderable.update();
}
@@ -564,15 +711,20 @@ export class PostprocessingPass {
this.transparentBackground = value;
}
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render');
this.updateState(camera, transparentBackground, backgroundColor, props);
this.updateState(camera, transparentBackground, backgroundColor, props, light);
if (props.outline.name === 'on') {
this.outlinesTarget.bind();
this.outlinesRenderable.render();
}
if (props.shadow.name === 'on') {
this.shadowsTarget.bind();
this.shadowsRenderable.render();
}
// don't render occlusion if offset is given,
// which will reuse the existing occlusion
if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {

View File

@@ -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>
*/
@@ -11,7 +11,7 @@ import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { createTexture, loadImageTexture, Texture } from '../../mol-gl/webgl/texture';
import { loadImageTexture, Texture } from '../../mol-gl/webgl/texture';
import { Vec2, Vec4 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -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);
@@ -191,8 +192,8 @@ function getWeightsRenderable(ctx: WebGLContext, edgesTexture: Texture): Weights
const width = edgesTexture.getWidth();
const height = edgesTexture.getHeight();
const areaTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgb', 'ubyte', 'linear');
const searchTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgba', 'ubyte', 'nearest');
const areaTexture = ctx.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
const searchTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
const values: Values<typeof WeightsSchema> = {
...QuadValues,

View File

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

View File

@@ -158,6 +158,7 @@ export namespace Cylinders {
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
};
@@ -245,6 +246,7 @@ export namespace Cylinders {
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
dSolidInterior: ValueCell.create(props.solidInterior),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
};
@@ -263,6 +265,7 @@ export namespace Cylinders {
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -21,6 +21,7 @@ export type OverpaintData = {
uOverpaintGridDim: ValueCell<Vec3>,
uOverpaintGridTransform: ValueCell<Vec4>,
dOverpaintType: ValueCell<string>,
uOverpaintStrength: ValueCell<number>,
}
export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) {
@@ -54,6 +55,7 @@ export function createOverpaint(count: number, type: OverpaintType, overpaintDat
uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dOverpaintType: ValueCell.create(type),
uOverpaintStrength: ValueCell.create(1),
};
}
}
@@ -74,6 +76,7 @@ export function createEmptyOverpaint(overpaintData?: OverpaintData): OverpaintDa
uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dOverpaintType: ValueCell.create('groupInstance'),
uOverpaintStrength: ValueCell.create(1),
};
}
}

View File

@@ -130,6 +130,7 @@ export namespace Spheres {
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
};
@@ -212,6 +213,7 @@ export namespace Spheres {
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
dSolidInterior: ValueCell.create(props.solidInterior),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
};
@@ -230,6 +232,7 @@ export namespace Spheres {
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -21,6 +21,7 @@ export type SubstanceData = {
uSubstanceGridDim: ValueCell<Vec3>,
uSubstanceGridTransform: ValueCell<Vec4>,
dSubstanceType: ValueCell<string>,
uSubstanceStrength: ValueCell<number>,
}
export function applySubstanceMaterial(array: Uint8Array, start: number, end: number, material: Material) {
@@ -54,6 +55,7 @@ export function createSubstance(count: number, type: SubstanceType, substanceDat
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create(type),
uSubstanceStrength: ValueCell.create(1),
};
}
}
@@ -74,6 +76,7 @@ export function createEmptySubstance(substanceData?: SubstanceData): SubstanceDa
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create('groupInstance'),
uSubstanceStrength: ValueCell.create(1),
};
}
}

View File

@@ -1,5 +1,5 @@
/**
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -88,7 +88,7 @@ export class FontAtlas {
this.scratchCanvas.width = this.maxWidth;
this.scratchCanvas.height = this.lineHeight;
this.scratchContext = this.scratchCanvas.getContext('2d')!;
this.scratchContext = this.scratchCanvas.getContext('2d', { willReadFrequently: true })!;
this.scratchContext.font = `${p.fontStyle} ${p.fontVariant} ${p.fontWeight} ${fontSize}px ${p.fontFamily}`;
this.scratchContext.fillStyle = 'black';
this.scratchContext.textBaseline = 'middle';

View File

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

View File

@@ -7,7 +7,7 @@
import { ValueCell } from '../../../mol-util';
import { Sphere3D } from '../../../mol-math/geometry';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
import { TransformData } from '../transform-data';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
@@ -20,11 +20,12 @@ import { createEmptyTransparency } from '../transparency-data';
import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { createNullTexture, Texture } from '../../../mol-gl/webgl/texture';
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { createEmptySubstance } from '../substance-data';
import { RenderableState } from '../../../mol-gl/renderable';
import { WebGLContext } from '../../../mol-gl/webgl/context';
export interface TextureMesh {
readonly kind: 'texture-mesh',
@@ -43,7 +44,10 @@ export interface TextureMesh {
readonly boundingSphere: Sphere3D
readonly meta: { [k: string]: unknown }
readonly meta: {
webgl?: WebGLContext
[k: string]: unknown
}
}
export namespace TextureMesh {
@@ -131,9 +135,42 @@ export namespace TextureMesh {
updateBoundingSphere,
createRenderableState,
updateRenderableState,
createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
createPositionIterator,
};
const TextureMeshName = 'texture-mesh';
function createPositionIterator(textureMesh: TextureMesh, transform: TransformData): LocationIterator {
const webgl = textureMesh.meta.webgl;
if (!webgl) return LocationIterator(1, 1, 1, () => NullLocation);
if (!webgl.namedFramebuffers[TextureMeshName]) {
webgl.namedFramebuffers[TextureMeshName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[TextureMeshName];
const [width, height] = textureMesh.geoTextureDim.ref.value;
const vertices = new Float32Array(width * height * 4);
framebuffer.bind();
textureMesh.vertexTexture.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, vertices);
const groupCount = textureMesh.vertexCount;
const instanceCount = transform.instanceCount.ref.value;
const location = PositionLocation();
const p = location.position;
const v = vertices;
const m = transform.aTransform.ref.value;
const getLocation = (groupIndex: number, instanceIndex: number) => {
if (instanceIndex < 0) {
Vec3.fromArray(p, v, groupIndex * 4);
} else {
Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 4, instanceIndex * 16);
}
return location;
};
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
function createValues(textureMesh: TextureMesh, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextureMeshValues {
const { instanceCount, groupCount } = locationIt;
const positionIt = Utils.createPositionIterator(textureMesh, transform);

View File

@@ -21,6 +21,7 @@ export type TransparencyData = {
uTransparencyGridDim: ValueCell<Vec3>,
uTransparencyGridTransform: ValueCell<Vec4>,
dTransparencyType: ValueCell<string>,
uTransparencyStrength: ValueCell<number>,
}
export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) {
@@ -63,6 +64,7 @@ export function createTransparency(count: number, type: TransparencyType, transp
uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dTransparencyType: ValueCell.create(type),
uTransparencyStrength: ValueCell.create(1),
};
}
}
@@ -84,6 +86,7 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr
uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dTransparencyType: ValueCell.create('groupInstance'),
uTransparencyStrength: ValueCell.create(1),
};
}
}

View File

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

View File

@@ -197,15 +197,14 @@ 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 });
//
// return at least a count of one to avoid issues downstram
const count = Math.max(1, getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture));
const height = Math.ceil(count / Math.pow(2, levels));
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height);
// console.log('height', height, 'finalCount', count, 'scale', scale);
// console.log({ height, count, scale });
return { pyramidTex, count, height, levels, scale };
}

View File

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

View File

@@ -142,9 +142,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
if (isWebGL2(gl)) {
if (!vertexTexture) {
vertexTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!groupTexture) {
@@ -199,6 +197,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, normalize: true });
// printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75, normalize: true });
// printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75, normalize: true });
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
}

View File

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

View File

@@ -27,6 +27,7 @@ export const CylindersSchema = {
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
dSolidInterior: DefineSpec('boolean'),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),
};

View File

@@ -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>
@@ -223,6 +229,7 @@ export const OverpaintSchema = {
uOverpaintGridTransform: UniformSpec('v4'),
tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
dOverpaintType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uOverpaintStrength: UniformSpec('f', 'material'),
} as const;
export type OverpaintSchema = typeof OverpaintSchema
export type OverpaintValues = Values<OverpaintSchema>
@@ -237,6 +244,7 @@ export const TransparencySchema = {
uTransparencyGridTransform: UniformSpec('v4'),
tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uTransparencyStrength: UniformSpec('f', 'material'),
} as const;
export type TransparencySchema = typeof TransparencySchema
export type TransparencyValues = Values<TransparencySchema>
@@ -250,6 +258,7 @@ export const SubstanceSchema = {
uSubstanceGridTransform: UniformSpec('v4'),
tSubstanceGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
dSubstanceType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uSubstanceStrength: UniformSpec('f', 'material'),
} as const;
export type SubstanceSchema = typeof SubstanceSchema
export type SubstanceValues = Values<SubstanceSchema>
@@ -327,4 +336,4 @@ export const BaseSchema = {
invariantBoundingSphere: ValueSpec('sphere'),
} as const;
export type BaseSchema = typeof BaseSchema
export type BaseValues = Values<BaseSchema>
export type BaseValues = Values<BaseSchema>

View File

@@ -24,6 +24,7 @@ export const SpheresSchema = {
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
dSolidInterior: DefineSpec('boolean'),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),
};

View File

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

View File

@@ -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';
@@ -53,6 +54,7 @@ export const enum MarkingType {
interface Renderer {
readonly stats: RendererStats
readonly props: Readonly<RendererProps>
readonly light: Readonly<Light>
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => void
clearDepth: (packed?: boolean) => void
@@ -70,6 +72,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
@@ -99,13 +104,13 @@ export const RendererParams = {
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
light: PD.ObjectList({
inclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
azimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
inclination: PD.Numeric(150, { min: 0, max: 180, step: 1 }),
azimuth: PD.Numeric(320, { min: 0, max: 360, step: 1 }),
color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
intensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
}, o => Color.toHexString(o.color), { defaultValue: [{
inclination: 180,
azimuth: 0,
inclination: 150,
azimuth: 320,
color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
intensity: 0.6
}] }),
@@ -114,7 +119,7 @@ export const RendererParams = {
};
export type RendererProps = PD.Values<typeof RendererParams>
type Light = {
export type Light = {
count: number
direction: number[]
color: number[]
@@ -141,7 +146,7 @@ namespace Renderer {
const enum Flag {
None = 0,
BlendedFront = 1,
BlendedBack = 2
BlendedBack = 2,
}
const enum Mask {
@@ -268,7 +273,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 +607,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 +715,9 @@ namespace Renderer {
renderBlendedVolume,
renderWboitOpaque,
renderWboitTransparent,
renderDpoitOpaque,
renderDpoitTransparent,
renderDpoitVolume,
setProps: (props: Partial<RendererProps>) => {
if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
@@ -755,6 +828,9 @@ namespace Renderer {
instancedDrawCount: stats.instancedDrawCount,
};
},
get light(): Light {
return light;
},
dispose: () => {
// TODO
}
@@ -762,4 +838,4 @@ namespace Renderer {
}
}
export { Renderer };
export { Renderer };

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ precision mediump sampler2D;
#if defined(dVariant_skybox)
uniform samplerCube tSkybox;
uniform mat4 uViewDirectionProjectionInverse;
uniform float uBlur;
uniform float uOpacity;
uniform float uSaturation;
uniform float uLightness;
@@ -49,7 +50,11 @@ vec3 lightenColor(vec3 c, float amount) {
void main() {
#if defined(dVariant_skybox)
vec4 t = uViewDirectionProjectionInverse * vPosition;
gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w));
#ifdef enabledShaderTextureLod
gl_FragColor = textureCubeLodEXT(tSkybox, normalize(t.xyz / t.w), uBlur * 8.0);
#else
gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w));
#endif
gl_FragColor.a = uOpacity;
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);
#elif defined(dVariant_image)

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

View File

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

View File

@@ -12,7 +12,7 @@ export const apply_light_color = `
gl_FragColor = material;
#else
#ifdef bumpEnabled
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) {
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0 && bumpiness > 0.0) {
normal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency);
}
#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.01, 0.99); // prevents black artifacts on specular highlight with transparent background
gl_FragColor = vec4(outgoingLight, color.a);
#endif

View File

@@ -42,6 +42,7 @@ export const assign_color_varying = `
#else
vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
vOverpaint *= uOverpaintStrength;
#endif
#ifdef dSubstance
@@ -58,6 +59,7 @@ export const assign_color_varying = `
// pre-mix to avoid artifacts due to empty substance
vSubstance.rgb = mix(vec3(uMetalness, uRoughness, uBumpiness), vSubstance.rgb, vSubstance.a);
vSubstance *= uSubstanceStrength;
#endif
#elif defined(dRenderVariant_pick)
#ifdef requiredDrawBuffers
@@ -86,5 +88,6 @@ export const assign_color_varying = `
vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;
#endif
vTransparency *= uTransparencyStrength;
#endif
`;

View File

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

View File

@@ -1,12 +1,7 @@
export const clip_instance = `
#if defined(dClipVariant_instance) && dClipObjectCount != 0
int flag = 0;
#if defined(dClipping)
flag = int(floor(vClipping * 255.0 + 0.5));
#endif
vec4 mCenter = uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0);
if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w), flag))
if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w)))
// move out of [ -w, +w ] to 'discard' in vert shader
gl_Position.z = 2.0 * gl_Position.w;
#endif

View File

@@ -1,12 +1,6 @@
export const clip_pixel = `
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
#if defined(dClipping)
int clippingFlag = int(floor(vClipping * 255.0 + 0.5));
#else
int clippingFlag = 0;
#endif
if (clipTest(vec4(vModelPosition, 0.0), clippingFlag))
if (clipTest(vec4(vModelPosition, 0.0)))
discard;
#endif
`;

View File

@@ -39,6 +39,7 @@ uniform float uBumpiness;
uniform vec4 uOverpaintGridTransform;
uniform sampler2D tOverpaintGrid;
#endif
uniform float uOverpaintStrength;
#endif
#ifdef dSubstance
@@ -53,6 +54,7 @@ uniform float uBumpiness;
uniform vec4 uSubstanceGridTransform;
uniform sampler2D tSubstanceGrid;
#endif
uniform float uSubstanceStrength;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100 || !defined(dVaryingGroup)
@@ -86,5 +88,6 @@ uniform float uBumpiness;
uniform vec4 uTransparencyGridTransform;
uniform sampler2D tTransparencyGrid;
#endif
uniform float uTransparencyStrength;
#endif
`;

View File

@@ -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 Ludovic Autin <autin@scripps.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,45 +7,45 @@
export const common_clip = `
#if dClipObjectCount != 0
vec3 quaternionTransform(vec4 q, vec3 v) {
vec3 quaternionTransform(const in vec4 q, const in vec3 v) {
vec3 t = 2.0 * cross(q.xyz, v);
return v + q.w * t + cross(q.xyz, t);
}
vec4 computePlane(vec3 normal, vec3 inPoint) {
vec4 computePlane(const in vec3 normal, const in vec3 inPoint) {
return vec4(normalize(normal), -dot(normal, inPoint));
}
float planeSD(vec4 plane, vec3 center) {
float planeSD(const in vec4 plane, const in vec3 center) {
return -dot(plane.xyz, center - plane.xyz * -plane.w);
}
float sphereSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
float sphereSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
return (
length(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position) / size) - 1.0
) * min(min(size.x, size.y), size.z);
}
float cubeSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
float cubeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 d = abs(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position)) - size;
return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
float cylinderSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
float cylinderSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
vec2 d = abs(vec2(length(t.xz), t.y)) - size.xy;
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
float infiniteConeSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
float infiniteConeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
float q = length(t.xy);
return dot(size.xy, vec2(q, t.z));
}
float getSignedDistance(vec3 center, int type, vec3 position, vec4 rotation, vec3 scale) {
float getSignedDistance(const in vec3 center, const in int type, const in vec3 position, const in vec4 rotation, const in vec3 scale) {
if (type == 1) {
vec3 normal = quaternionTransform(rotation, vec3(0.0, 1.0, 0.0));
vec4 plane = computePlane(normal, position);
@@ -65,7 +65,7 @@ export const common_clip = `
#if __VERSION__ == 100
// 8-bit
int bitwiseAnd(int a, int b) {
int bitwiseAnd(const in int a, const in int b) {
int d = 128;
int result = 0;
for (int i = 0; i < 8; ++i) {
@@ -78,17 +78,23 @@ export const common_clip = `
return result;
}
bool hasBit(int mask, int bit) {
bool hasBit(const in int mask, const in int bit) {
return bitwiseAnd(mask, bit) == 0;
}
#else
bool hasBit(int mask, int bit) {
bool hasBit(const in int mask, const in int bit) {
return (mask & bit) == 0;
}
#endif
// flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
bool clipTest(vec4 sphere, int flag) {
bool clipTest(const in vec4 sphere) {
// flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
#if defined(dClipping)
int flag = int(floor(vClipping * 255.0 + 0.5));
#else
int flag = 0;
#endif
#pragma unroll_loop_start
for (int i = 0; i < dClipObjectCount; ++i) {
if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {

View File

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

View File

@@ -44,10 +44,9 @@ varying vec3 vModelPosition;
varying vec3 vViewPosition;
#if defined(noNonInstancedActiveAttribs)
#define VertexID gl_VertexID // for testing
// // int() is needed for some Safari versions
// // see https://bugs.webkit.org/show_bug.cgi?id=244152
// #define VertexID int(gl_VertexID)
// int() is needed for some Safari versions
// see https://bugs.webkit.org/show_bug.cgi?id=244152
#define VertexID int(gl_VertexID)
#else
attribute float aVertex;
#define VertexID int(aVertex)

View File

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

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

View File

@@ -28,12 +28,19 @@ uniform mat4 uInvView;
#include light_frag_params
#include common_clip
#ifdef dSolidInterior
const bool solidInterior = true;
#else
const bool solidInterior = false;
#endif
// adapted from https://www.shadertoy.com/view/4lcSRn
// The MIT License, Copyright 2016 Inigo Quilez
bool CylinderImpostor(
in vec3 rayOrigin, in vec3 rayDir,
in vec3 start, in vec3 end, in float radius,
out vec4 intersection, out bool interior
out vec3 cameraNormal, out bool interior,
out vec3 modelPosition, out vec3 viewPosition, out float fragmentDepth
){
vec3 ba = end - start;
vec3 oc = rayOrigin - start;
@@ -42,7 +49,7 @@ bool CylinderImpostor(
float bard = dot(ba, rayDir);
float baoc = dot(ba, oc);
float k2 = baba - bard*bard;
float k2 = baba - bard * bard;
float k1 = baba * dot(oc, rayDir) - baoc * bard;
float k0 = baba * dot(oc, oc) - baoc * baoc - radius * radius * baba;
@@ -52,71 +59,185 @@ bool CylinderImpostor(
bool topCap = (vCap > 0.9 && vCap < 1.1) || vCap >= 2.9;
bool bottomCap = (vCap > 1.9 && vCap < 2.1) || vCap >= 2.9;
#ifdef dSolidInterior
bool topInterior = !topCap;
bool bottomInterior = !bottomCap;
topCap = true;
bottomCap = true;
#else
bool topInterior = false;
bool bottomInterior = false;
#endif
bool clipped = false;
bool objectClipped = false;
// body outside
h = sqrt(h);
float t = (-k1 - h) / k2;
float y = baoc + t * bard;
if (y > 0.0 && y < baba) {
interior = false;
intersection = vec4(t, (oc + t * rayDir - ba * y / baba) / radius);
return true;
cameraNormal = (oc + t * rayDir - ba * y / baba) / radius;
modelPosition = rayOrigin + t * rayDir;
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(vec4(modelPosition, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
#ifdef dSolidInterior
topCap = !topInterior;
bottomCap = !bottomInterior;
#endif
}
#endif
if (fragmentDepth > 0.0) return true;
clipped = true;
}
if (topCap && y < 0.0) {
// top cap
t = -baoc / bard;
if (abs(k1 + k2 * t) < h) {
interior = false;
intersection = vec4(t, ba * sign(y) / baba);
return true;
}
} else if(bottomCap && y >= 0.0) {
// bottom cap
t = (baba - baoc) / bard;
if (abs(k1 + k2 * t) < h) {
interior = false;
intersection = vec4(t, ba * sign(y) / baba);
return true;
if (!clipped) {
if (topCap && y < 0.0) {
// top cap
t = -baoc / bard;
if (abs(k1 + k2 * t) < h) {
interior = topInterior;
cameraNormal = -ba / baba;
modelPosition = rayOrigin + t * rayDir;
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(vec4(modelPosition, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
#ifdef dSolidInterior
topCap = !topInterior;
bottomCap = !bottomInterior;
#endif
}
#endif
if (fragmentDepth > 0.0) {
#ifdef dSolidInterior
if (interior) cameraNormal = -rayDir;
#endif
return true;
}
}
} else if (bottomCap && y >= 0.0) {
// bottom cap
t = (baba - baoc) / bard;
if (abs(k1 + k2 * t) < h) {
interior = bottomInterior;
cameraNormal = ba / baba;
modelPosition = rayOrigin + t * rayDir;
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
if (clipTest(vec4(modelPosition, 0.0))) {
objectClipped = true;
fragmentDepth = -1.0;
#ifdef dSolidInterior
topCap = !topInterior;
bottomCap = !bottomInterior;
#endif
}
#endif
if (fragmentDepth > 0.0) {
#ifdef dSolidInterior
if (interior) cameraNormal = -rayDir;
#endif
return true;
}
}
}
}
if (uDoubleSided) {
if (uDoubleSided || solidInterior) {
// body inside
h = -h;
t = (-k1 - h) / k2;
y = baoc + t * bard;
if (y > 0.0 && y < baba) {
interior = true;
intersection = vec4(t, (oc + t * rayDir - ba * y / baba) / radius);
return true;
cameraNormal = -(oc + t * rayDir - ba * y / baba) / radius;
modelPosition = rayOrigin + t * rayDir;
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
if (fragmentDepth > 0.0) {
#ifdef dSolidInterior
if (!objectClipped) {
fragmentDepth = 0.0 + (0.0000002 / vSize);
cameraNormal = -rayDir;
}
#endif
return true;
}
}
// TODO: handle inside caps???
if (topCap && y < 0.0) {
// top cap
t = -baoc / bard;
if (abs(k1 + k2 * t) < -h) {
interior = true;
cameraNormal = ba / baba;
modelPosition = rayOrigin + t * rayDir;
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
if (fragmentDepth > 0.0) {
#ifdef dSolidInterior
if (!objectClipped) {
fragmentDepth = 0.0 + (0.0000002 / vSize);
cameraNormal = -rayDir;
}
#endif
return true;
}
}
} else if (bottomCap && y >= 0.0) {
// bottom cap
t = (baba - baoc) / bard;
if (abs(k1 + k2 * t) < -h) {
interior = true;
cameraNormal = -ba / baba;
modelPosition = rayOrigin + t * rayDir;
viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
fragmentDepth = calcDepth(viewPosition);
if (fragmentDepth > 0.0) {
#ifdef dSolidInterior
if (!objectClipped) {
fragmentDepth = 0.0 + (0.0000002 / vSize);
cameraNormal = -rayDir;
}
#endif
return true;
}
}
}
}
return false;
}
void main() {
#include clip_pixel
vec3 rayOrigin = vModelPosition;
vec3 rayDir = mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
vec4 intersection;
bool interior;
bool hit = CylinderImpostor(vModelPosition, rayDir, vStart, vEnd, vSize, intersection, interior);
vec3 cameraNormal;
vec3 modelPosition;
vec3 viewPosition;
float fragmentDepth;
bool hit = CylinderImpostor(rayOrigin, rayDir, vStart, vEnd, vSize, cameraNormal, interior, modelPosition, viewPosition, fragmentDepth);
if (!hit) discard;
vec3 vViewPosition = vModelPosition + intersection.x * rayDir;
vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz;
gl_FragDepthEXT = calcDepth(vViewPosition);
if (fragmentDepth < 0.0) discard;
if (fragmentDepth > 1.0) discard;
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
gl_FragDepthEXT = fragmentDepth;
if (gl_FragDepthEXT < 0.0) discard;
if (gl_FragDepthEXT > 1.0) discard;
vec3 vViewPosition = viewPosition;
vec3 vModelPosition = modelPosition;
float fragmentDepth = gl_FragDepthEXT;
#include clip_pixel
#include assign_material_color
#if defined(dRenderVariant_pick)
@@ -135,13 +256,14 @@ void main() {
gl_FragColor = material;
#elif defined(dRenderVariant_color)
mat3 normalMatrix = transpose3(inverse3(mat3(uView)));
vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw));
vec3 normal = normalize(normalMatrix * -normalize(cameraNormal));
#include apply_light_color
#include apply_interior_color
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -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>
*/
@@ -55,7 +55,9 @@ void main() {
vec3 camDir = -mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
vec3 dir = vEnd - vStart;
// ensure cylinder 'dir' is pointing towards the camera
if(dot(camDir, dir) < 0.0) dir = -dir;
if(dot(camDir, dir) < 0.0) {
dir = -dir;
}
vec3 left = cross(camDir, dir);
vec3 up = cross(left, dir);
@@ -69,6 +71,9 @@ void main() {
vViewPosition = mvPosition.xyz;
gl_Position = uProjection * mvPosition;
mvPosition.z -= 2.0 * (length(vEnd - vStart) + vSize); // avoid clipping
gl_Position.z = (uProjection * mvPosition).z;
#include clip_instance
}
`;

View File

@@ -229,7 +229,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
if (clipTest(vec4(vModelPosition, 0.0))) {
prevValue = value;
pos += step;
continue;
@@ -270,16 +270,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#elif defined(dColorType_groupInstance)
material.rgb = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
#elif defined(dColorType_vertex)
material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
material.rgb = texture3dFrom1dTrilinear(tColor, unitPos, uGridDim, uColorTexDim, 0.0).rgb;
#elif defined(dColorType_vertexInstance)
material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
material.rgb = texture3dFrom1dTrilinear(tColor, unitPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
#endif
#ifdef dOverpaint
#if defined(dOverpaintType_groupInstance)
overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
#elif defined(dOverpaintType_vertexInstance)
overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount));
overpaint = texture3dFrom1dTrilinear(tOverpaint, unitPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount));
#endif
material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
@@ -356,4 +356,4 @@ void main() {
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
#include wboit_write
}
`;
`;

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

View File

@@ -159,6 +159,7 @@ void main() {
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -39,6 +39,7 @@ void main(){
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -62,6 +62,7 @@ void main() {
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -38,7 +38,11 @@ float getDepthOpaque(const in vec2 coords) {
}
float getDepthTransparent(const in vec2 coords) {
return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
#ifdef dTransparentOutline
return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
#else
return 1.0;
#endif
}
bool isBackground(const in float depth) {

Some files were not shown because too many files have changed in this diff Show More