Compare commits

...

173 Commits

Author SHA1 Message Date
dsehnal
ab34a59677 3.41.0 2023-10-15 13:23:58 +02:00
dsehnal
7a96cdd52d changelog 2023-10-15 13:20:01 +02:00
midlik
65cad5ea4d Assembly Symmetry extension customization (#950)
* Show assembly symmetry from PDBe API (quick and dirty)

* Treat 404 from PDBe API as success

* RCSBAssemblySymmetry extension: make defaults configurable via plugin config items

* RCSBAssemblySymmetry: revert configs to default values

* RCSBAssemblySymmetry: correctly handle non-assembly structure with PDBe API
2023-10-15 13:05:50 +02:00
Alexander Rose
a765ba8e3b Merge pull request #949 from JonStargaryen/master
Fix layout typo
2023-10-10 21:49:30 -07:00
Sebastian Bittrich
8594ce80a9 cl 2023-10-10 10:04:23 -07:00
Sebastian Bittrich
915797c4a4 fix layout typo 2023-10-10 09:49:24 -07:00
Alexander Rose
44c69f538b fix partial polymer trace sec-struc type 2023-10-08 22:06:35 -07:00
Alexander Rose
e4396039fd SetUtils performance tweaks 2023-10-07 13:44:02 -07:00
David Sehnal
e548a3ed85 add PluginContext.initialized promise (#935) 2023-10-02 19:46:02 +02:00
Alexander Rose
fc44e66b26 3.40.1 2023-09-30 10:53:23 -07:00
Alexander Rose
98f3f5a23b changelog 2023-09-30 10:50:23 -07:00
Alexander Rose
f2f10d0cb5 3.40.0 2023-09-30 10:47:30 -07:00
Alexander Rose
aed1056d6c Merge pull request #932 from molstar/sharpening
Sharpening
2023-09-30 10:42:59 -07:00
Alexander Rose
be47ac09c9 schema updates 2023-09-30 10:37:27 -07:00
Alexander Rose
d5e7797a40 package updates 2023-09-30 10:33:44 -07:00
Alexander Rose
0aeac628c7 Merge pull request #929 from molstar/better-bounding-spheres
fix bounding sphere calculations for "element-like" visuals
2023-09-30 10:21:03 -07:00
Alexander Rose
668d617cd7 Merge branch 'master' into better-bounding-spheres 2023-09-30 10:20:46 -07:00
Alexander Rose
62ed993f0d changelog 2023-09-30 10:19:23 -07:00
Alexander Rose
aa0a008a41 gracefully handle missing HTMLImageElement 2023-09-30 10:02:12 -07:00
Alexander Rose
89f01f202d add sharpening postprocessing pass 2023-09-30 09:39:15 -07:00
Alexander Rose
733190f7a0 scale outline by pixelRatio 2023-09-30 09:38:44 -07:00
Alexander Rose
50429aacfa fix setSize not always applied to passes 2023-09-30 09:37:58 -07:00
David Sehnal
fa541bdbd3 hide right panel (#922) 2023-09-29 14:51:48 +02:00
Sebastian Bittrich
77d173afed Update RCSB PDB validation report URL (#930)
* update RCSB PDB valrep URL

* cl

* https & more tweaks
2023-09-29 14:51:07 +02:00
dsehnal
a934001ae8 fix bounding sphere calculations 2023-09-28 18:18:48 +02:00
midlik
e5d4606437 Add blockIndex parameter to TrajectoryFromMmCif (#928) 2023-09-28 16:45:19 +02:00
Alexander Rose
fb16cd0070 typo 2023-09-16 22:06:08 -07:00
Alexander Rose
c427549b8d add support for webgl extensions
- EXT_conservative_depth
- WEBGL_stencil_texturing
- EXT_clip_control
2023-09-16 21:47:08 -07:00
Alexander Rose
310300bde8 add alphaThickness parameter for spheres 2023-09-16 12:13:34 -07:00
Alexander Rose
11604b9e8f add MultiSampleParams.reduceFlicker 2023-09-16 12:08:38 -07:00
Alexander Rose
cc1bf482f2 add support for WEBGL_clip_cull_distance 2023-09-09 12:27:22 -07:00
Alexander Rose
61a351b3d4 Merge pull request #906 from JonStargaryen/atomcount
ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
2023-09-09 10:58:06 -07:00
Alexander Rose
9e91a242bf Merge branch 'master' into atomcount 2023-09-09 10:57:53 -07:00
Alexander Rose
c3daa1a162 Merge pull request #907 from JonStargaryen/ccd-aromatic
CCD extension: Make visuals for aromatic bonds configurable
2023-09-09 10:56:35 -07:00
Alexander Rose
fe086fb62e Merge branch 'master' into ccd-aromatic 2023-09-09 10:56:23 -07:00
Alexander Rose
c2217829a3 improve Canvas3DContext types 2023-09-09 10:54:14 -07:00
David Sehnal
6333c8073f Add optional CifFile to MmcifFormat.data (#912)
* Add optional CifFile to MmcifFormat.data

* fix
2023-09-08 19:24:18 +02:00
Sebastian Bittrich
2801bcf111 CCD extension: Make visuals for aromatic bonds configurable 2023-09-06 15:20:45 -07:00
Sebastian Bittrich
8a2461e157 global cl 2023-09-05 14:09:21 -07:00
Sebastian Bittrich
0a081e2a8a ModelServer version 2023-09-05 14:07:19 -07:00
Sebastian Bittrich
700a3fe95c ligand queries: fix atom count reported by SDF/MOL/MOL2 export 2023-09-05 14:06:07 -07:00
dsehnal
febc634d8b fix changelog 2023-09-05 18:49:52 +02:00
David Sehnal
0105f75bb6 InputObserver tap => click (#901) 2023-09-05 18:47:52 +02:00
David Sehnal
4cc2073eaa fix updateFocusRepr (#903) 2023-09-05 06:01:47 +02:00
Alexander Rose
9ac204cb6e 3.39.0 2023-09-02 12:16:11 -07:00
Alexander Rose
73378bbe9d schema updates 2023-09-02 12:08:52 -07:00
Alexander Rose
9b5fd2595c package updates 2023-09-02 12:06:21 -07:00
Alexander Rose
bca2073ed0 changelog 2023-09-02 11:34:26 -07:00
Alexander Rose
f264e4d6b8 webgl tweaks
- export getBuffer
- add glEnumToString helper
2023-09-02 11:30:09 -07:00
Sebastian Bittrich
795222b5b4 Allow toggling of hydrogens as part of LabelTextVisual (#900)
* Allow toggling of hydrogens as part of `LabelTextVisual`

* fix typo
2023-08-30 10:51:51 +02:00
Alexander Rose
25eb4450ad support iv2/3/4 uniforms 2023-08-27 12:08:57 -07:00
Alexander Rose
140df13dae improve texture print helper 2023-08-27 12:08:39 -07:00
Alexander Rose
792cd513a8 faster bounding rectangle for imposter spheres 2023-08-27 09:40:52 -07:00
Shinn
14e6172c33 add some elements name for guessElementSymbolString function (#883) 2023-08-03 16:25:47 +02:00
Alexander Rose
911433e056 3.38.3 2023-07-29 23:10:31 -07:00
Alexander Rose
6b585cf0d6 fix imposter spheres not updating 2023-07-29 23:07:18 -07:00
Alexander Rose
86211aaf3a 3.38.2 2023-07-24 22:58:57 -07:00
Alexander Rose
071623f5b6 changelog 2023-07-24 22:56:08 -07:00
Alexander Rose
21e514ec1e fix non-physical keys support 2023-07-24 22:55:32 -07:00
Alexander Rose
9bc0ab12e7 Merge pull request #878 from JonStargaryen/ccd-fix
Fix logic for trajectoryFromCCD
2023-07-24 22:54:17 -07:00
Sebastian Bittrich
1d1bd05400 cl 2023-07-24 13:38:22 -07:00
Sebastian Bittrich
faa750bbf9 allow entries with chem_comp_atom - fixes #877 2023-07-24 13:33:48 -07:00
Alexander Rose
e92e5c5cef 3.38.1 2023-07-22 17:41:29 -07:00
Alexander Rose
b49230ea1f fix pixel-scale not updated in SSAO pass 2023-07-22 17:37:35 -07:00
dsehnal
44ebc1d39a 3.38.0 2023-07-18 17:39:42 +02:00
dsehnal
8d8e45f4ce changelog 2023-07-18 17:35:56 +02:00
Alexander Rose
898d877aa1 support non-physical keys in bindings trigger code (#860)
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-07-18 17:34:53 +02:00
David Sehnal
85dba9b1a4 ability to disable extension in the default viewer (#872) 2023-07-18 17:33:33 +02:00
dsehnal
6b5e90c5fa Merge branch 'master' of https://github.com/molstar/molstar 2023-07-17 09:40:25 +02:00
dsehnal
e231fbf3d7 add LRUCache.remove 2023-07-17 09:40:06 +02:00
David Sehnal
0ee8525b2d update getStateSnapshot behavior (#858)
* update getStateSnapshot behavior

* update syncCurrent
2023-07-13 19:38:35 +02:00
midlik
106ee614e7 Add 'Chain Instance' and 'Uniform' options for 'Carbon Color' param (#868) 2023-07-12 09:31:35 +02:00
David Sehnal
34056751f9 fix distinct palette getSamples (#857) 2023-07-11 08:52:36 +02:00
Alexander Rose
1afea8a86a Merge pull request #863 from molstar/approx-spheres
add approximate option for spheres rendering
2023-07-09 20:43:29 -07:00
Alexander Rose
96d5bf2447 Merge branch 'master' of https://github.com/molstar/molstar into approx-spheres 2023-07-09 20:38:10 -07:00
Alexander Rose
f9265a7049 fix clipping of approximate spheres 2023-07-09 20:31:55 -07:00
Alexander Rose
5c57137890 fix bitwiseAnd for glsl 1.00 2023-07-09 19:02:30 -07:00
Alexander Rose
4e71618d0f Merge pull request #864 from molstar/spheres-mem
reduce spheres geo memory usage
2023-07-09 18:55:14 -07:00
Alexander Rose
de660cc233 Merge branch 'master' into spheres-mem 2023-07-09 18:55:07 -07:00
Alexander Rose
616a1dabfa Merge pull request #859 from molstar/protein-caps
add more common protein caps
2023-07-09 18:47:36 -07:00
Alexander Rose
46ea39703f Merge branch 'master' into protein-caps 2023-07-09 18:47:30 -07:00
Alexander Rose
6cf20d0c44 Merge pull request #861 from molstar/snapshot-opened-event
add opened event to PluginStateSnapshotManager
2023-07-09 18:46:39 -07:00
Alexander Rose
0737e23b70 Merge branch 'master' into snapshot-opened-event 2023-07-09 18:46:30 -07:00
Alexander Rose
70d0c15d28 Merge pull request #862 from molstar/euler
add euler math primitive
2023-07-09 18:46:03 -07:00
Alexander Rose
9272c8c5ec Merge branch 'master' into euler 2023-07-09 18:45:55 -07:00
Alexander Rose
a3349f82fc Merge pull request #865 from molstar/element-stride
add stride option to element sphere & point visuals
2023-07-09 18:45:27 -07:00
Alexander Rose
4d399edbdd add stride option to element sphere & point visuals 2023-07-09 11:25:56 -07:00
Alexander Rose
64598eba96 fix vertex count 2023-07-09 11:04:36 -07:00
Alexander Rose
aa25874775 reduce spheres geo memory usage
- derive mapping from VertexID
- pull position and group from texture
2023-07-08 22:27:45 -07:00
Alexander Rose
dccc06d497 tweaks to approximate spheres rendering 2023-07-08 16:19:02 -07:00
Alexander Rose
c000526cf8 add approximate option for spheres rendering 2023-07-08 16:05:24 -07:00
Alexander Rose
2166ab455c add euler math primitive 2023-07-08 15:41:14 -07:00
Alexander Rose
4de9ce01fc package updates 2023-07-08 15:40:20 -07:00
Alexander Rose
f543fd5683 exports 2023-07-08 13:25:56 -07:00
Alexander Rose
8535013ee5 add scissors icon 2023-07-08 13:25:29 -07:00
Alexander Rose
320ab77f8e properly switch-off fog 2023-07-08 13:23:39 -07:00
Alexander Rose
982feef0c6 add opened event to PluginStateSnapshotManager 2023-07-08 12:05:51 -07:00
Alexander Rose
bd6d04cefb add more common protein caps 2023-07-08 12:00:24 -07:00
David Sehnal
5e1c351efc fix display issue with sifts mapping (#854)
* fix display issue with sifts mapping

* update header
2023-07-06 20:30:01 +02:00
Alexander Rose
61a294c889 3.37.1 2023-06-20 22:02:56 -07:00
Alexander Rose
71fbd6baab changelog 2023-06-20 21:58:53 -07:00
Alexander Rose
33430a836a fix lines, text, points rendering 2023-06-20 21:58:32 -07:00
Alexander Rose
f428e9f39e fix issues with wboit/dpoit in large scenes
- remove unneeded depth check (depth texture support required for wboit/dpoit)
2023-06-18 16:15:24 -07:00
Alexander Rose
2d26425cbe 3.37.0 2023-06-16 23:46:03 -07:00
Alexander Rose
f6030aee25 changelog 2023-06-16 23:42:22 -07:00
Alexander Rose
609e03f7d2 add mipmap-based blur for image backgrounds 2023-06-16 23:12:13 -07:00
Alexander Rose
ba12a8bbee add contextHash to SizeTheme 2023-06-16 22:29:38 -07:00
Alexander Rose
947f293844 Merge pull request #839 from molstar/xray-inverted
add inverted xray-shaded option
2023-06-16 22:26:55 -07:00
Alexander Rose
fbff0e769c Merge branch 'master' into xray-inverted 2023-06-16 22:26:47 -07:00
Alexander Rose
3798223d39 Merge pull request #840 from molstar/model-export-name
Model export name
2023-06-16 22:26:22 -07:00
Alexander Rose
ac9c23dc65 3.36.1-model-export-name.0 2023-06-12 22:22:35 -07:00
Alexander Rose
096f492ccb add ability to set a file name for structures 2023-06-12 22:09:13 -07:00
Alexander Rose
ba96da9354 add inverted xray-shaded option 2023-06-11 15:31:24 -07:00
Alexander Rose
6c1d17bac5 3.36.1 2023-06-11 12:44:43 -07:00
Alexander Rose
ad2ccf4e07 fix changelog 2023-06-11 12:41:59 -07:00
Alexander Rose
dc1b7b4693 3.36.0 2023-06-11 12:28:28 -07:00
Alexander Rose
59e4e2b31d schema updates 2023-06-11 12:25:05 -07:00
Alexander Rose
d2483dc449 package updates 2023-06-11 12:23:42 -07:00
Alexander Rose
d26946e9ee Merge pull request #838 from molstar/cartoon-color-nucleic
Cartoon improvements
2023-06-05 23:06:06 -07:00
Alexander Rose
cd045a6b48 fix spec 2023-06-05 22:49:43 -07:00
Alexander Rose
2407729d27 simplify sub color theme creation 2023-06-05 22:43:51 -07:00
Alexander Rose
1aa22b9fa0 add saturation/lightness to uniform color theme 2023-06-05 22:42:34 -07:00
Alexander Rose
35c9f39a69 add cartoon color theme
- separates colorings for for mainchain and sidechain visuals
- uses isSecondary mechanism of LocationIterator
2023-06-03 11:15:33 -07:00
Alexander Rose
7dd420cc18 add nucleicProfile param to cartoon repr 2023-06-03 11:13:08 -07:00
Alexander Rose
1d434c259a ignore LociLabelManager without providers 2023-05-29 15:01:26 -07:00
Alexander Rose
6d193edd68 add keyReleased event 2023-05-29 15:01:14 -07:00
Alexander Rose
9bf859d6ed Merge pull request #835 from JonStargaryen/channels
Adjust VolumeRepresentation#setState
2023-05-29 11:41:33 -07:00
Sebastian Bittrich
207230d565 check against _state 2023-05-29 11:16:17 -07:00
Alexander Rose
b7a673f38e Merge pull request #731 from JonStargaryen/master
Parse CCD Files
2023-05-29 11:11:57 -07:00
Sebastian Bittrich
2204e4e0d0 merge upstream 2023-05-29 09:24:11 -07:00
Sebastian Bittrich
6276365766 update setState of unit repr 2023-05-29 09:18:07 -07:00
Sebastian Bittrich
505b04c92d merge upstream 2023-05-29 08:56:37 -07:00
Alexander Rose
fc84dcb037 Merge pull request #836 from molstar/structure-selection-snapshot
add snapshot support for structure selections
2023-05-28 12:32:37 -07:00
Alexander Rose
2f29ff7314 Merge pull request #826 from molstar/graph-bonds
Graph & bonds calc tweaks & fixes
2023-05-27 11:07:56 -07:00
Alexander Rose
b37f043876 Merge branch 'master' into graph-bonds 2023-05-27 11:07:45 -07:00
Alexander Rose
f0e725f65c tweak CCD coordinate type handling 2023-05-27 11:01:27 -07:00
Alexander Rose
23a34e2df1 add snapshot support for structure selections 2023-05-27 10:14:46 -07:00
Alexander Rose
d11e242b70 fix background occlusion handling 2023-05-27 10:07:58 -07:00
Sebastian Bittrich
d9af0ca068 cl 2023-05-26 14:04:31 -07:00
Sebastian Bittrich
b7f10acbf0 adjust VolumeRepresentation#setState - closes #210 2023-05-26 13:59:45 -07:00
Sebastian Bittrich
43749ccdbd merge 2023-05-23 09:24:46 -07:00
David Sehnal
3bf4a8f8e6 optimize computeInterUnitBonds (#830) 2023-05-23 09:08:00 +02:00
Alexander Rose
f0ae1b3347 fix EdgeBuilder.addNextEdge for loop edges 2023-05-22 23:27:06 -07:00
Alexander Rose
99809d25b9 remove erroneous bounding-box overlap test 2023-05-22 23:25:08 -07:00
Sebastian Bittrich
e83c0af67c meta 2023-05-22 13:10:47 -07:00
Sebastian Bittrich
2ddf94313e Merge remote-tracking branch 'upstream/master' 2023-05-22 13:08:41 -07:00
Sebastian Bittrich
da5965c956 meta 2023-05-22 13:04:58 -07:00
Sebastian Bittrich
31be0af3c9 shrink diff 2023-05-22 12:55:23 -07:00
Sebastian Bittrich
38c550b245 factor out to extension 2023-05-22 11:59:57 -07:00
Sebastian Bittrich
95a7a2cef9 rm CCDCoordinateTypeProp 2023-05-22 09:16:28 -07:00
Alexander Rose
1a1ec51736 fix bbox overlap test in Structure.eachUnitPair 2023-05-20 22:46:59 -07:00
Alexander Rose
299aae56c1 typing improvements 2023-05-20 19:22:04 -07:00
Alexander Rose
781824c961 operators in IndexPairBonds as directed property 2023-05-20 19:21:40 -07:00
Sebastian Bittrich
930cfa2590 comment 2023-05-15 11:45:39 -07:00
Sebastian Bittrich
35439f01aa rather heavy-handed tracking of ideal/model coords 2023-05-15 11:44:04 -07:00
Sebastian Bittrich
3d96298b55 Merge remote-tracking branch 'upstream/master' 2023-05-12 14:45:40 -07:00
Sebastian Bittrich
964f045e56 refactor 2023-05-12 11:52:18 -07:00
Sebastian Bittrich
d3364ac109 control what coord set to show 2023-05-12 11:28:51 -07:00
Sebastian Bittrich
a5b963c919 hide hydrogens by default 2023-05-12 09:41:56 -07:00
Sebastian Bittrich
22f9bc4ff1 swap ideal/model labels, handle entirely missing coords 2023-05-11 15:42:13 -07:00
Sebastian Bittrich
c6c4350638 align differing atom sets 2023-05-11 11:28:40 -07:00
Sebastian Bittrich
1b0401dff5 filter for present valuekind 2023-05-09 14:50:37 -07:00
JonStargaryen
0295e0ef63 factor out CoordinateType 2023-05-05 09:41:46 -07:00
Sebastian Bittrich
6e82405600 fix merge 2023-05-04 16:25:52 -07:00
Sebastian Bittrich
a678893bdb wip 2023-05-04 16:20:28 -07:00
Sebastian Bittrich
66b4fcdc2c simplify 2023-03-23 08:48:58 -07:00
Sebastian Bittrich
1f3e20704d better applicable check 2023-03-15 16:05:06 -07:00
Sebastian Bittrich
cc9bdd4f14 add ccd hierarchy preset 2023-03-15 16:01:54 -07:00
Sebastian Bittrich
fbc74c0012 Merge remote-tracking branch 'upstream/master' 2023-02-24 14:27:24 -08:00
Sebastian Bittrich
27a953795c use ComponentBond.Provider 2023-02-24 14:17:48 -08:00
Sebastian Bittrich
6ada52bc0b names 2023-02-21 14:33:37 -08:00
Sebastian Bittrich
c526cb9f08 consolidate params 2023-02-21 13:51:26 -08:00
Sebastian Bittrich
a1662d76fb parse CCD files 2023-02-21 13:41:40 -08:00
138 changed files with 8463 additions and 6766 deletions

View File

@@ -6,6 +6,100 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.41.0] - 2023-10-15
- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
- Fix undesired interaction between settings panel and the panel on the right.
- Add ability to customize server parameters for `RCSBAssemblySymmetry`.
## [v3.40.1] - 2023-09-30
- Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.
- Treat "tap" as a click in `InputObserver`
- ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
- CCD extension: Make visuals for aromatic bonds configurable
- Add optional `file?: CifFile` to `MmcifFormat.data`
- Add support for webgl extensions
- `WEBGL_clip_cull_distance`
- `EXT_conservative_depth`
- `WEBGL_stencil_texturing`
- `EXT_clip_control`
- Add `MultiSampleParams.reduceFlicker` (to be able to switch it off)
- Add `alphaThickness` parameter to adjust alpha of spheres for radius
- Ability to hide "right" panel from simplified viewport controls
- Add `blockIndex` parameter to TrajectoryFromMmCif
- Fix bounding sphere calculation for "element-like" visuals
- Fix RCSB PDB validation report URL
- Add sharpening postprocessing option
- Take pixel-ratio into account for outline scale
- Gracefully handle missing HTMLImageElement
- Fix pixel-ratio changes not applied to all render passes
## [v3.39.0] - 2023-09-02
- Add some elements support for `guessElementSymbolString` function
- Faster bounding rectangle calculation for imposter spheres
- Allow toggling of hydrogens as part of `LabelTextVisual`
## [v3.38.3] - 2023-07-29
- Fix imposter spheres not updating, e.g. in trajectories (broke in v3.38.0)
## [v3.38.2] - 2023-07-24
- Don't rely solely on `chem_comp_atom` when detecting CCD files (#877)
- Actually support non-physical keys in `Bindings.Trigger.code`
## [v3.38.1] - 2023-07-22
- Fix pixel-scale not updated in SSAO pass
## [v3.38.0] - 2023-07-18
- Fix display issue with SIFTS mapping
- Support non-physical keys in `Bindings.Trigger.code`
- Update `getStateSnapshot` to only overwrite current snapshot if it was created automatically
- Fix distinct palette's `getSamples` infinite loop
- Add 'NH2', 'FOR', 'FMT' to `CommonProteinCaps`
- Add `opened` event to `PluginStateSnapshotManager`
- Properly switch-off fog
- Add `approximate` option for spheres rendering
- Reduce `Spheres` memory usage
- Derive mapping from VertexID
- Pull position and group from texture
- Add `Euler` math primitive
- Add stride option to element sphere & point visuals
- Add `disabledExtensions` field to default viewer's options
- Add `LRUCache.remove`
- Add 'Chain Instance' and 'Uniform' options for 'Carbon Color' param (in Color Theme: Element Symbol)
## [v3.37.1] - 2023-06-20
- Fix issues with wboit/dpoit in large scenes
- Fix lines, text, points rendering (broken in v3.37.0)
## [v3.37.0] - 2023-06-17
- Add `inverted` option to `xrayShaded` parameter
- Model-export extension: Add ability to set a file name for structures
- Add `contextHash` to `SizeTheme`
- Add mipmap-based blur for image backgrounds
## [v3.36.1] - 2023-06-11
- Allow parsing of CCD ligand files
- Add dedicated wwPDB CCD extension to align and visualize ideal & model CCD coordinates
- Make operators in `IndexPairBonds` a directed property
- Remove erroneous bounding-box overlap test in `Structure.eachUnitPair`
- Fix `EdgeBuilder.addNextEdge` for loop edges
- Optimize inter unit bond compute
- Ensure consistent state for volume representation (#210)
- Improve SSAO for thin geometry (e.g. lines)
- Add snapshot support for structure selections
- Add `nucleicProfile` parameter to cartoon representation
- Add `cartoon` theme with separate colorings for for mainchain and sidechain visuals
## [v3.35.0] - 2023-05-14
- Enable odd dash count (1,3,5)

6675
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.35.0",
"version": "3.41.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -103,57 +103,57 @@
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^4.0.1",
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/time": "^4.0.0",
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
"@graphql-codegen/typescript-graphql-request": "^4.5.9",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@types/cors": "^2.8.13",
"@types/gl": "^6.0.2",
"@graphql-codegen/add": "^5.0.0",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/time": "^5.0.0",
"@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-graphql-files-modules": "^3.0.0",
"@graphql-codegen/typescript-graphql-request": "^6.0.0",
"@graphql-codegen/typescript-operations": "^4.0.1",
"@types/cors": "^2.8.14",
"@types/gl": "^6.0.3",
"@types/jpeg-js": "^0.3.7",
"@types/pngjs": "^6.0.1",
"@types/jest": "^29.5.1",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"@typescript-eslint/eslint-plugin": "^5.59.5",
"@typescript-eslint/parser": "^5.59.5",
"@types/pngjs": "^6.0.2",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.8",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"benchmark": "^2.1.4",
"concurrently": "^8.0.1",
"cpx2": "^4.2.3",
"concurrently": "^8.2.1",
"cpx2": "^5.0.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.3",
"eslint": "^8.40.0",
"css-loader": "^6.8.1",
"eslint": "^8.50.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^11.1.1",
"graphql": "^16.6.0",
"graphql": "^16.8.1",
"http-server": "^14.1.1",
"jest": "^29.5.0",
"mini-css-extract-plugin": "^2.7.5",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.7.6",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.62.1",
"sass-loader": "^13.2.2",
"simple-git": "^3.18.0",
"sass": "^1.68.0",
"sass-loader": "^13.3.2",
"simple-git": "^3.20.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.2",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4",
"webpack": "^5.82.1",
"webpack-cli": "^5.1.1"
"style-loader": "^3.3.3",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.2",
"@types/compression": "1.7.2",
"@types/express": "^4.17.17",
"@types/node": "^16.18.30",
"@types/node-fetch": "^2.6.3",
"@types/swagger-ui-dist": "3.30.1",
"@types/argparse": "^2.0.11",
"@types/benchmark": "^2.1.3",
"@types/compression": "1.7.3",
"@types/express": "^4.17.18",
"@types/node": "^16.18.55",
"@types/node-fetch": "^2.6.6",
"@types/swagger-ui-dist": "3.30.2",
"argparse": "^2.0.1",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
@@ -161,11 +161,11 @@
"express": "^4.18.2",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.21",
"immutable": "^4.3.0",
"node-fetch": "^2.6.11",
"immutable": "^4.3.4",
"node-fetch": "^2.7.0",
"rxjs": "^7.8.1",
"swagger-ui-dist": "^4.18.3",
"tslib": "^2.5.0",
"swagger-ui-dist": "^5.9.0",
"tslib": "^2.6.2",
"util.promisify": "^1.1.2",
"xhr2": "^0.2.1"
},

View File

@@ -50,6 +50,8 @@ import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/car
import { Backgrounds } from '../../extensions/backgrounds';
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
import { RCSBAssemblySymmetryConfig } from '../../extensions/rcsb/assembly-symmetry/behavior';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
@@ -58,7 +60,7 @@ const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
export const ExtensionMap = {
'volseg': PluginSpec.Behavior(Volseg),
'backgrounds': PluginSpec.Behavior(Backgrounds),
'cellpack': PluginSpec.Behavior(CellPack),
@@ -74,11 +76,13 @@ const Extensions = {
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
};
const DefaultViewerOptions = {
customFormats: CustomFormats as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
extensions: ObjectKeys(ExtensionMap),
disabledExtensions: [] as string[],
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
@@ -111,6 +115,9 @@ const DefaultViewerOptions = {
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
rcsbAssemblySymmetryDefaultServerType: RCSBAssemblySymmetryConfig.DefaultServerType.defaultValue,
rcsbAssemblySymmetryDefaultServerUrl: RCSBAssemblySymmetryConfig.DefaultServerUrl.defaultValue,
rcsbAssemblySymmetryApplyColors: RCSBAssemblySymmetryConfig.ApplyColors.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
@@ -129,11 +136,13 @@ export class Viewer {
const o: ViewerOptions = { ...DefaultViewerOptions, ...definedOptions };
const defaultSpec = DefaultPluginUISpec();
const disabledExtension = new Set(o.disabledExtensions ?? []);
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
...o.extensions.filter(e => !disabledExtension.has(e)).map(e => ExtensionMap[e]),
],
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
@@ -186,6 +195,9 @@ export class Viewer {
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
[RCSBAssemblySymmetryConfig.DefaultServerType, o.rcsbAssemblySymmetryDefaultServerType],
[RCSBAssemblySymmetryConfig.DefaultServerUrl, o.rcsbAssemblySymmetryDefaultServerUrl],
[RCSBAssemblySymmetryConfig.ApplyColors, o.rcsbAssemblySymmetryApplyColors],
]
};

View File

@@ -65,7 +65,10 @@
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
// console.log('Available extensions: ', Object.keys(molstar.ExtensionMap));
molstar.Viewer.create('app', {
disabledExtensions: [], // anything from Object.keys(molstar.ExtensionMap)
layoutShowControls: !hideControls,
viewportShowExpand: false,
collapseLeftPanel: collapseLeftPanel,

View File

@@ -81,5 +81,5 @@ export const DefaultDataOptions: DataOptions = {
const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data');
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
const CCD_URL = 'https://files.wwpdb.org/pub/pdb/data/monomers/components.cif';
const PVCD_URL = 'https://files.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';

View File

@@ -22,6 +22,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'uline':
case 'uchar3':
case 'uchar1':
case 'uchar5':
// only force lower-case for enums
return values && values.length ? EnumCol(values.map(x => x.toLowerCase()), 'lstr', description) : StrCol(description);
case 'aliasname':
@@ -61,6 +62,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'symop':
case 'exp_data_doi':
case 'asym_id':
case 'uniprot_ptm_id':
return StrCol(description);
case 'int':
case 'non_negative_int':
@@ -89,6 +91,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'Tag':
case 'Implied':
case 'Word':
case 'Uri':
return wrapContainer('str', ',', description, container);
case 'Real':
return wrapContainer('float', ',', description, container);

View File

@@ -50,7 +50,7 @@ const _testBasis: Basis = {
0.025886090588624934,
0.019164790004065606,
-0.013539970104105408
] as Vec3,
],
'shells': [
{
'angularMomentum': [0],
@@ -101,7 +101,7 @@ const _testBasis: Basis = {
0.5082729578468134,
1.6880351220025265,
0.4963443067810461
] as Vec3,
],
'shells': [
{
'angularMomentum': [0],
@@ -158,7 +158,7 @@ const _testBasis: Basis = {
1.1367367844436005,
-0.47018519422670163,
-1.356802622574504
] as Vec3,
],
'shells': [
{
'angularMomentum': [0],

View File

@@ -53,7 +53,7 @@ export async function sphericalCollocation(
L,
shell.coefficients[amIndex++],
shell.exponents,
atom.center,
atom.center as unknown as Vec3,
cutoffThreshold,
alpha
);

View File

@@ -22,7 +22,7 @@ export interface SphericalElectronShell {
export interface Basis {
atoms: {
// in Bohr units!
center: Vec3;
center: [number, number, number];
shells: SphericalElectronShell[];
}[];
}
@@ -78,7 +78,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const count = geometry.length;
const box = Box3D.expand(
Box3D(),
Box3D.fromVec3Array(Box3D(), geometry),
Box3D.fromVec3Array(Box3D(), geometry as unknown as Vec3[]),
Vec3.create(expand, expand, expand)
);
const size = Box3D.size(Vec3(), box);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -28,6 +28,12 @@ export const Backgrounds = PluginBehavior.create<{ }>({
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.config.set(PluginConfig.Background.Styles, [
[{
variant: {
name: 'off',
params: {}
}
}, 'Off'],
[{
variant: {
name: 'radialGradient',
@@ -50,6 +56,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
lightness: 0,
saturation: 0,
opacity: 1,
blur: 0,
coverage: 'viewport',
}
}

View File

@@ -465,13 +465,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
const center = Vec3();
const aPosition = values.aPosition.ref.value;
const aGroup = values.aGroup.ref.value;
const aPosition = values.centerBuffer.ref.value;
const aGroup = values.groupBuffer.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
const meshes: Mesh[] = [];
const sphereCount = vertexCount / 4 * instanceCount;
const sphereCount = vertexCount / 6 * instanceCount;
let detail: number;
switch (this.options.primitivesQuality) {
case 'auto':
@@ -495,7 +495,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 4) {
for (let i = 0; i < sphereCount; ++i) {
v3fromArray(center, aPosition, i * 3);
const group = aGroup[i];

View File

@@ -1,17 +1,28 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
import { to_mmCIF, Unit } from '../../mol-model/structure';
import { Structure, to_mmCIF, Unit } from '../../mol-model/structure';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { getFormattedTime } from '../../mol-util/date';
import { download } from '../../mol-util/download';
import { zip } from '../../mol-util/zip/zip';
const ModelExportNameProp = '__ModelExportName__';
export const ModelExport = {
getStructureName(structure: Structure): string | undefined {
return structure.inheritedPropertyData[ModelExportNameProp];
},
setStructureName(structure: Structure, name: string) {
return structure.inheritedPropertyData[ModelExportNameProp] = name;
}
};
export async function exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
try {
await plugin.runTask(_exportHierarchy(plugin, options), { useOverlay: true });
@@ -43,19 +54,21 @@ function _exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'b
continue;
}
const name = entryMap.has(s.model.entryId)
? `${s.model.entryId}_${entryMap.get(s.model.entryId)! + 1}.${format}`
: `${s.model.entryId}.${format}`;
entryMap.set(s.model.entryId, (entryMap.get(s.model.entryId) ?? 0) + 1);
const name = ModelExport.getStructureName(s) || s.model.entryId || 'unnamed';
await ctx.update({ message: `Exporting ${s.model.entryId}...`, isIndeterminate: true, canAbort: false });
const fileName = entryMap.has(name)
? `${name}_${entryMap.get(name)! + 1}.${format}`
: `${name}.${format}`;
entryMap.set(name, (entryMap.get(name) ?? 0) + 1);
await ctx.update({ message: `Exporting ${name}...`, isIndeterminate: true, canAbort: false });
if (s.elementCount > 100000) {
// Give UI chance to update, only needed for larger structures.
await new Promise(res => setTimeout(res, 50));
}
try {
files.push([name, to_mmCIF(s.model.entryId, s, format === 'bcif', { copyAllCategories: true })]);
files.push([fileName, to_mmCIF(name, s, format === 'bcif', { copyAllCategories: true })]);
} catch (e) {
if (format === 'cif' && s.elementCount > 2000000) {
plugin.log.warn(`[Export] The structure might be too big to be exported as Text CIF, consider using the BinaryCIF format instead.`);

View File

@@ -5,12 +5,13 @@
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider, AssemblySymmetryDataParams } from './prop';
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
import { AssemblySymmetryClusterColorThemeProvider } from './color';
import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
import { Task } from '../../../mol-task';
import { PluginConfigItem } from '../../../mol-plugin/config';
import { PluginContext } from '../../../mol-plugin/context';
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
@@ -77,14 +78,15 @@ export const InitAssemblySymmetry3D = StateAction.build({
description: 'Initialize Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
},
from: PluginStateObject.Molecule.Structure,
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data),
params: (a, plugin: PluginContext) => getConfiguredDefaultParams(plugin)
})(({ a, ref, state, params }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
try {
const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
await AssemblySymmetryDataProvider.attach(propCtx, a.data);
await AssemblySymmetryDataProvider.attach(propCtx, a.data, params);
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
await AssemblySymmetryProvider.attach(propCtx, a.data, { ...params, symmetryIndex });
} catch (e) {
plugin.log.error(`Assembly Symmetry: ${e}`);
return;
@@ -152,10 +154,6 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
//
export const AssemblySymmetryPresetParams = {
...StructureRepresentationPresetProvider.CommonParams,
};
export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-rcsb-assembly-symmetry',
display: {
@@ -165,7 +163,12 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
isApplicable(a) {
return AssemblySymmetry.isApplicable(a.data);
},
params: () => AssemblySymmetryPresetParams,
params: (a, plugin) => {
return {
...StructureRepresentationPresetProvider.CommonParams,
...getConfiguredDefaultParams(plugin)
};
},
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
@@ -174,15 +177,16 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
if (!AssemblySymmetryDataProvider.get(structure).value) {
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
const propCtx = { runtime, assetManager: plugin.managers.asset };
await AssemblySymmetryDataProvider.attach(propCtx, structure);
const propProps = { serverType: params.serverType, serverUrl: params.serverUrl };
await AssemblySymmetryDataProvider.attach(propCtx, structure, propProps);
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
await AssemblySymmetryProvider.attach(propCtx, structure, { ...propProps, symmetryIndex });
}));
}
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
const colorTheme = getRCSBAssemblySymmetryConfig(plugin).ApplyColors && assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
@@ -194,4 +198,27 @@ export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: Stat
const assemblySymmetry = state.build().to(structure)
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
return assemblySymmetry.commit({ revertOnError: true });
}
}
//
export const RCSBAssemblySymmetryConfig = {
DefaultServerType: new PluginConfigItem('rcsb-assembly-symmetry.server-type', AssemblySymmetryDataParams.serverType.defaultValue),
DefaultServerUrl: new PluginConfigItem('rcsb-assembly-symmetry.server-url', AssemblySymmetryDataParams.serverUrl.defaultValue),
ApplyColors: new PluginConfigItem('rcsb-assembly-symmetry.apply-colors', true),
};
export function getRCSBAssemblySymmetryConfig(plugin: PluginContext): { [key in keyof typeof RCSBAssemblySymmetryConfig]: NonNullable<typeof RCSBAssemblySymmetryConfig[key]['defaultValue']> } {
return {
ApplyColors: plugin.config.get(RCSBAssemblySymmetryConfig.ApplyColors) ?? RCSBAssemblySymmetryConfig.ApplyColors.defaultValue ?? true,
DefaultServerType: plugin.config.get(RCSBAssemblySymmetryConfig.DefaultServerType) ?? RCSBAssemblySymmetryConfig.DefaultServerType.defaultValue ?? AssemblySymmetryDataParams.serverType.defaultValue,
DefaultServerUrl: plugin.config.get(RCSBAssemblySymmetryConfig.DefaultServerUrl) ?? RCSBAssemblySymmetryConfig.DefaultServerUrl.defaultValue ?? AssemblySymmetryDataParams.serverUrl.defaultValue,
};
}
function getConfiguredDefaultParams(plugin: PluginContext) {
const config = getRCSBAssemblySymmetryConfig(plugin);
const params = PD.clone(AssemblySymmetryDataParams);
PD.setDefaultValues(params, { serverType: config.DefaultServerType, serverUrl: config.DefaultServerUrl });
return params;
}

View File

@@ -20,6 +20,7 @@ import { SetUtils } from '../../../mol-util/set';
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
import { compile } from '../../../mol-script/runtime/query/compiler';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { Asset } from '../../../mol-util/assets';
const BiologicalAssemblyNames = new Set([
'author_and_software_defined_assembly',
@@ -48,7 +49,7 @@ export namespace AssemblySymmetry {
Representation = 'rcsb-assembly-symmetry-3d'
}
export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
export const DefaultServerUrl = 'https://data.rcsb.org/graphql'; // Alternative: 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry' (if serverType is 'pdbe')
export function isApplicable(structure?: Structure): boolean {
return (
@@ -61,6 +62,8 @@ export namespace AssemblySymmetry {
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
if (!isApplicable(structure)) return { value: [] };
if (props.serverType === 'pdbe') return fetch_PDBe(ctx, structure, props);
const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
const variables: AssemblySymmetryQueryVariables = {
assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
@@ -77,6 +80,37 @@ export namespace AssemblySymmetry {
return { value, assets: [result] };
}
async function fetch_PDBe(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
const assembly_id = structure.units[0].conformation.operator.assembly?.id || '-1'; // should use '' instead of '-1' but the API does not support non-number assembly_id
const entry_id = structure.units[0].model.entryId.toLowerCase();
const url = `${props.serverUrl}/${entry_id}?assembly_id=${assembly_id}`;
const asset = Asset.getUrlAsset(ctx.assetManager, url);
let dataWrapper: Asset.Wrapper<'json'>;
try {
dataWrapper = await ctx.assetManager.resolve(asset, 'json').runInContext(ctx.runtime);
} catch (err) {
// PDBe API returns 404 when there are no symmetries -> treat as success with empty json in body
if (`${err}`.includes('404')) { // dirrrty
dataWrapper = Asset.Wrapper({}, asset, ctx.assetManager);
} else {
throw err;
}
}
const data = dataWrapper.data;
const value: AssemblySymmetryDataValue = (data[entry_id] ?? []).map((v: any) => ({
kind: 'Global Symmetry',
oligomeric_state: v.oligomeric_state,
stoichiometry: [v.stoichiometry],
symbol: v.symbol,
type: v.type,
clusters: [],
rotation_axes: v.rotation_axes,
}));
return { value, assets: [dataWrapper] };
}
/** Returns the index of the first non C1 symmetry or -1 */
export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
@@ -147,7 +181,8 @@ export function getSymmetrySelectParam(structure?: Structure) {
//
export const AssemblySymmetryDataParams = {
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
serverType: PD.Select('rcsb', [['rcsb', 'RCSB'], ['pdbe', 'PDBe']] as const),
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL (if server type is RCSB) or PDBe API endpoint URL (if server type is PDBe)' })
};
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
@@ -174,7 +209,7 @@ export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<Asse
function getAssemblySymmetryParams(data?: Structure) {
return {
... AssemblySymmetryDataParams,
...AssemblySymmetryDataParams,
symmetryIndex: getSymmetrySelectParam(data)
};
}

View File

@@ -6,7 +6,7 @@
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry, getRCSBAssemblySymmetryConfig } from './behavior';
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -72,6 +72,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
get params() {
const structure = this.pivot.cell.obj?.data;
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
params.serverType.isHidden = true;
params.serverUrl.isHidden = true;
return params;
}
@@ -111,7 +112,9 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
}
} else {
tryCreateAssemblySymmetry(this.plugin, s.cell);
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
if (getRCSBAssemblySymmetryConfig(this.plugin).ApplyColors) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
}
}
}
}
@@ -151,5 +154,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
const EnableAssemblySymmetry3D = StateAction.build({
from: PluginStateObject.Molecule.Structure,
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
const presetParams = AssemblySymmetryPreset.params?.(a, plugin) as PD.Params | undefined;
const presetProps = presetParams ? PD.getDefaultValues(presetParams) : Object.create(null);
await AssemblySymmetryPreset.apply(ref, presetProps, plugin);
}));

File diff suppressed because it is too large Load Diff

View File

@@ -85,7 +85,7 @@ namespace ValidationReport {
Clashes = 'rcsb-clashes',
}
export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports';
export const DefaultBaseUrl = 'https://files.rcsb.org/pub/pdb/validation_reports';
export function getEntryUrl(pdbId: string, baseUrl: string) {
const id = pdbId.toLowerCase();
return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`;

View File

@@ -0,0 +1,47 @@
# Chemical Component Dictionary Extension
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) describes all small molecules and monomers found in PDB entries. The dictionary provides a plethora of additional information not present in wwPDB archive structures such as chemical descriptors (SMILES & InChI) and stereochemical assignments, information on bond order and more. Most notably, the CCD provides 2 sets of coordinates:
- `ideal`: idealized/minimized coordinates, obtained using Molecular Networks' Corina, and if there are issues, OpenEye's OMEGA
- `model`: coordinates extracted from an archive structure
## How to Load a Component from URL
1. "Download Structure" -- switch "Source" to "URL"
2. Enter URL of component, e.g. https://files.rcsb.org/ligands/view/HEM.cif, leave "Format" as is
3. Click "Apply"
This parses the corresponding component into 2 models (1st: `ideal` coordinates, 2nd: `model` coordinates) and applies the default representaiton to the 1st model. `model` coordinates are available as 2nd model. Click the canvas to re-focus if you don't see anything after switching models due to the coordinates being far away.
## How to Visualize Components
There's a dedicated representation preset that faciliates the comparison of `ideal` and `model` coordinates.
1. Load a component as described above
2. Switch structure preset to "Chemical Component" (button in the top-right, in the "Structure" panel)
This creates a dedicated component for `ideal` as well as `model` coordinates and represents them as ball-and-stick. Initially, only `ideal` coordinates are shown. After toggling the visibility of `model` coordinates, they appear superimposed with the `ideal` coordinates.
## Examples & Test Cases
Ligand | Description | Details
-- | -- | --
https://files.rcsb.org/ligands/view/HEM.cif | metal coordination |
https://files.rcsb.org/ligands/view/FE.cif | +3 oxidation state |
https://files.rcsb.org/ligands/view/FE2.cif | +2 oxidation state |
https://files.rcsb.org/ligands/view/RUC.cif | transition metal |
https://files.rcsb.org/ligands/view/SF4.cif | Fe-S cluster | doesn't align nicely
https://files.rcsb.org/ligands/view/TBR.cif | coords identical |
https://files.rcsb.org/ligands/view/OER.cif | coords identical |
https://files.rcsb.org/ligands/view/FEA.cif | charges |
https://files.rcsb.org/ligands/view/PR2.cif | orientation differs |
https://files.rcsb.org/ligands/view/03R.cif | some atoms missing |
https://files.rcsb.org/ligands/view/02U.cif | many atoms missing |
https://files.rcsb.org/ligands/view/HC0.cif | no ideal coords | unrelated: O and H atoms clashing
https://files.rcsb.org/ligands/view/Q6O.cif | no model coords |
https://files.rcsb.org/ligands/view/H0C.cif | big ligand |
https://files.rcsb.org/ligands/view/2NC.cif | dual representation as PRD and CC |
https://files.rcsb.org/birds/view/PRDCC_000001.cif | PRDCC |
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/BB87Q.cif | extended CCD identifier |
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/7ZTVU.cif | extended CCD identifier |
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/9QRZS.cif | extended CCD identifier |
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/9ABCD.cif | extended CCD identifier |
https://files.rcsb.org/ligands/view/UNK.cif | CCD special: unknown amino acid | unrelated: some model H are placed far away
https://files.rcsb.org/ligands/view/UNX.cif | CCD special: unknown atom/ion | no ideal coordinates
https://files.rcsb.org/ligands/view/UNL.cif | CCD special: unknown ligand | no coordinates whatsoever

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
import { ChemicalComponentPreset, ChemicalCompontentTrajectoryHierarchyPreset } from './representation';
export const wwPDBChemicalComponentDictionary = PluginBehavior.create<{ }>({
name: 'wwpdb-chemical-component-dictionary',
category: 'representation',
display: {
name: 'wwPDB Chemical Compontent Dictionary',
description: 'Custom representation for data loaded from the CCD.'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.builders.structure.hierarchy.registerPreset(ChemicalCompontentTrajectoryHierarchyPreset);
this.ctx.builders.structure.representation.registerPreset(ChemicalComponentPreset);
}
update() {
return false;
}
unregister() {
this.ctx.builders.structure.hierarchy.unregisterPreset(ChemicalCompontentTrajectoryHierarchyPreset);
this.ctx.builders.structure.representation.unregisterPreset(ChemicalComponentPreset);
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,167 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { StateObjectRef, StateTransform } from '../../../mol-state';
import { StateTransforms } from '../../../mol-plugin-state/transforms';
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../../mol-plugin-state/builder/structure/representation-preset';
import { PluginContext } from '../../../mol-plugin/context';
import { Mat4 } from '../../../mol-math/linear-algebra';
import { Structure } from '../../../mol-model/structure';
import { CCDFormat } from '../../../mol-model-formats/structure/mmcif';
import { MinimizeRmsd } from '../../../mol-math/linear-algebra/3d/minimize-rmsd';
import { SetUtils } from '../../../mol-util/set';
import { TrajectoryHierarchyPresetProvider } from '../../../mol-plugin-state/builder/structure/hierarchy-preset';
import { capitalize } from '../../../mol-util/string';
const CCDParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
representationPresetParams: PD.Optional(PD.Group(StructureRepresentationPresetProvider.CommonParams)),
showOriginalCoordinates: PD.Optional(PD.Boolean(true, { description: `Show original coordinates for 'model' and 'ideal' structure and do not align them.` })),
shownCoordinateType: PD.Select('ideal', PD.arrayToOptions(['ideal', 'model', 'both'] as const), { description: `What coordinate sets are visible.` }),
aromaticBonds: PD.Boolean(false, { description: 'Display aromatic bonds with dashes' }),
...TrajectoryHierarchyPresetProvider.CommonParams(a, plugin)
});
export const ChemicalCompontentTrajectoryHierarchyPreset = TrajectoryHierarchyPresetProvider({
id: 'preset-trajectory-ccd',
display: {
name: 'Chemical Component', group: 'Preset',
description: 'Shows molecules from the Chemical Component Dictionary.'
},
isApplicable: o => {
return CCDFormat.is(o.data.representative.sourceData);
},
params: CCDParams,
async apply(trajectory, params, plugin) {
const tr = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory)?.obj?.data;
if (!tr) return {};
const builder = plugin.builders.structure;
const idealModel = await builder.createModel(trajectory, { modelIndex: 0 });
const idealModelProperties = await builder.insertModelProperties(idealModel, params.modelProperties, { isCollapsed: true });
const idealStructure = await builder.createStructure(idealModelProperties || idealModel, { name: 'model', params: {} });
const idealStructureProperties = await builder.insertStructureProperties(idealStructure, params.structureProperties);
const representationPreset = params.representationPreset || ChemicalComponentPreset.id;
const representationPresetParams = params.representationPresetParams || {};
if (representationPresetParams.ignoreHydrogens === undefined) representationPresetParams.ignoreHydrogens = true;
// degenerate case where either model or ideal coordinates are missing
if (tr.frameCount !== 2) {
const coordinateType = CCDFormat.CoordinateType.get(idealModel.obj!.data);
await builder.representation.applyPreset(idealStructureProperties, representationPreset, { ...representationPresetParams, coordinateType });
return { models: [idealModel], structures: [idealStructure] };
}
const modelModel = await builder.createModel(trajectory, { modelIndex: 1 });
const modelModelProperties = await builder.insertModelProperties(modelModel, params.modelProperties, { isCollapsed: true });
const modelStructure = await builder.createStructure(modelModelProperties || modelModel, { name: 'model', params: {} });
const modelStructureProperties = await builder.insertStructureProperties(modelStructure, params.structureProperties);
// align ideal and model coordinates
if (!params.showOriginalCoordinates) {
const [a, b] = getPositionTables(idealStructure.obj!.data, modelStructure.obj!.data);
if (!a) {
plugin.log.warn(`Cannot align chemical components whose atom sets are disjoint.`);
} else {
const { bTransform, rmsd } = MinimizeRmsd.compute({ a, b });
await transform(plugin, modelStructure.cell!, bTransform);
plugin.log.info(`Superposed [model] and [ideal] with RMSD ${rmsd.toFixed(2)}.`);
}
}
await builder.representation.applyPreset(idealStructureProperties, representationPreset, { ...representationPresetParams, aromaticBonds: params.aromaticBonds, coordinateType: 'ideal', isHidden: params.shownCoordinateType === 'model' });
await builder.representation.applyPreset(modelStructureProperties, representationPreset, { ...representationPresetParams, aromaticBonds: params.aromaticBonds, coordinateType: 'model', isHidden: params.shownCoordinateType === 'ideal' });
return { models: [idealModel, modelModel], structures: [idealStructure, modelStructure] };
}
});
function getPositionTables(s1: Structure, s2: Structure) {
const m1 = getAtomIdSerialMap(s1);
const m2 = getAtomIdSerialMap(s2);
const intersecting = SetUtils.intersection(new Set(m1.keys()), new Set(m2.keys()));
const ret = [
MinimizeRmsd.Positions.empty(intersecting.size),
MinimizeRmsd.Positions.empty(intersecting.size)
];
let o = 0;
intersecting.forEach(k => {
ret[0].x[o] = s1.model.atomicConformation.x[m1.get(k)!];
ret[0].y[o] = s1.model.atomicConformation.y[m1.get(k)!];
ret[0].z[o] = s1.model.atomicConformation.z[m1.get(k)!];
ret[1].x[o] = s2.model.atomicConformation.x[m2.get(k)!];
ret[1].y[o] = s2.model.atomicConformation.y[m2.get(k)!];
ret[1].z[o] = s2.model.atomicConformation.z[m2.get(k)!];
o++;
});
return ret;
}
function getAtomIdSerialMap(structure: Structure) {
const map = new Map<string, number>();
const { label_atom_id } = structure.model.atomicHierarchy.atoms;
for (let i = 0, il = label_atom_id.rowCount; i < il; ++i) {
const id = label_atom_id.value(i);
if (!map.has(id)) map.set(id, map.size);
}
return map;
}
function transform(plugin: PluginContext, s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4) {
const b = plugin.state.data.build().to(s)
.insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
return plugin.runTask(plugin.state.data.updateTree(b));
}
export const ChemicalComponentPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-chemical-component',
display: {
name: 'Chemical Component', group: 'Miscellaneous',
description: `Show 'Ideal' and 'Model' coordinates of chemical components.`
},
isApplicable: o => {
return CCDFormat.is(o.data.model.sourceData);
},
params: () => ({
...StructureRepresentationPresetProvider.CommonParams,
aromaticBonds: PD.Boolean(true),
coordinateType: PD.Select<CCDFormat.CoordinateType>('ideal', PD.arrayToOptions(['ideal', 'model'])),
isHidden: PD.Boolean(false),
}),
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const { aromaticBonds, coordinateType, isHidden } = params;
const components = {
[coordinateType]: await presetStaticComponent(plugin, structureCell, 'all', { label: capitalize(coordinateType), tags: [coordinateType] })
};
const structure = structureCell.obj!.data;
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
[coordinateType]: builder.buildRepresentation(update, components[coordinateType], { type: 'ball-and-stick', typeParams: { ...typeParams, aromaticBonds } }, { initialState: { isHidden } }),
};
// sync UI state
if (components[coordinateType]?.cell?.state && isHidden) {
StateTransform.assignState(components[coordinateType]!.cell!.state, { isHidden });
}
await update.commit({ revertOnError: true });
await StructureRepresentationPresetProvider.updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
return { components, representations };
}
});

View File

@@ -107,13 +107,13 @@ export { Canvas3DContext };
/** Can be used to create multiple Canvas3D objects */
interface Canvas3DContext {
readonly canvas: HTMLCanvasElement
readonly canvas?: HTMLCanvasElement
readonly webgl: WebGLContext
readonly input: InputObserver
readonly passes: Passes
readonly attribs: Readonly<Canvas3DContext.Attribs>
readonly contextLost: BehaviorSubject<now.Timestamp>
readonly contextRestored: BehaviorSubject<now.Timestamp>
readonly contextLost?: BehaviorSubject<now.Timestamp>
readonly contextRestored?: BehaviorSubject<now.Timestamp>
readonly assetManager: AssetManager
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
@@ -445,7 +445,7 @@ namespace Canvas3D {
if (isTimingMode) webgl.timer.mark('Canvas3D.render', true);
const ctx = { renderer, camera: cam, scene, helper };
if (MultiSamplePass.isEnabled(p.multiSample)) {
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
const forceOn = p.multiSample.reduceFlicker && !cameraChanged && markingUpdated && !controls.isAnimating;
multiSampleHelper.render(ctx, p, true, forceOn);
} else {
passes.draw.render(ctx, p, true);

View File

@@ -638,45 +638,45 @@ namespace TrackballControls {
Vec2.copy(_rotCurr, getMouseOnCircle(movementX + cx, movementY + cy));
}
function onKeyDown({ modifiers, code, x, y }: KeyInput) {
function onKeyDown({ modifiers, code, key, x, y }: KeyInput) {
if (outsideViewport(x, y)) return;
if (Binding.matchKey(b.keyMoveForward, code, modifiers)) {
if (Binding.matchKey(b.keyMoveForward, code, modifiers, key)) {
keyState.moveForward = 1;
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers, key)) {
keyState.moveBack = 1;
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers, key)) {
keyState.moveLeft = 1;
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers, key)) {
keyState.moveRight = 1;
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers, key)) {
keyState.moveUp = 1;
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers, key)) {
keyState.moveDown = 1;
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers)) {
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers, key)) {
keyState.rollLeft = 1;
} else if (Binding.matchKey(b.keyRollRight, code, modifiers)) {
} else if (Binding.matchKey(b.keyRollRight, code, modifiers, key)) {
keyState.rollRight = 1;
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers)) {
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers, key)) {
keyState.pitchUp = 1;
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers)) {
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers, key)) {
keyState.pitchDown = 1;
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers)) {
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers, key)) {
keyState.yawLeft = 1;
} else if (Binding.matchKey(b.keyYawRight, code, modifiers)) {
} else if (Binding.matchKey(b.keyYawRight, code, modifiers, key)) {
keyState.yawRight = 1;
}
if (Binding.matchKey(b.boostMove, code, modifiers)) {
if (Binding.matchKey(b.boostMove, code, modifiers, key)) {
keyState.boostMove = 1;
}
if (Binding.matchKey(b.enablePointerLock, code, modifiers)) {
if (Binding.matchKey(b.enablePointerLock, code, modifiers, key)) {
input.requestPointerLock(viewport);
}
}
function onKeyUp({ modifiers, code, x, y }: KeyInput) {
function onKeyUp({ modifiers, code, key, x, y }: KeyInput) {
if (outsideViewport(x, y)) return;
let isModifierCode = false;
@@ -715,34 +715,34 @@ namespace TrackballControls {
}
for (const code of codes) {
if (Binding.matchKey(b.keyMoveForward, code, modifiers)) {
if (Binding.matchKey(b.keyMoveForward, code, modifiers, key)) {
keyState.moveForward = 0;
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers, key)) {
keyState.moveBack = 0;
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers, key)) {
keyState.moveLeft = 0;
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers, key)) {
keyState.moveRight = 0;
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers, key)) {
keyState.moveUp = 0;
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers)) {
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers, key)) {
keyState.moveDown = 0;
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers)) {
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers, key)) {
keyState.rollLeft = 0;
} else if (Binding.matchKey(b.keyRollRight, code, modifiers)) {
} else if (Binding.matchKey(b.keyRollRight, code, modifiers, key)) {
keyState.rollRight = 0;
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers)) {
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers, key)) {
keyState.pitchUp = 0;
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers)) {
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers, key)) {
keyState.pitchDown = 0;
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers)) {
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers, key)) {
keyState.yawLeft = 0;
} else if (Binding.matchKey(b.keyYawRight, code, modifiers)) {
} else if (Binding.matchKey(b.keyYawRight, code, modifiers, key)) {
keyState.yawRight = 0;
}
}
if (Binding.matchKey(b.boostMove, code, modifiers)) {
if (Binding.matchKey(b.boostMove, code, modifiers, key)) {
keyState.boostMove = 0;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -23,6 +23,7 @@ import { Vec2 } from '../../mol-math/linear-algebra/3d/vec2';
import { Color } from '../../mol-util/color';
import { Asset, AssetManager } from '../../mol-util/assets';
import { Vec4 } from '../../mol-math/linear-algebra/3d/vec4';
import { isPowerOfTwo } from '../../mol-math/misc';
const SharedParams = {
opacity: PD.Numeric(1, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -59,6 +60,7 @@ const ImageParams = {
url: PD.Text(''),
file: PD.File({ accept: 'image/*' }),
}),
blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or with power-of-two images and when "EXT_shader_texture_lod" is available.' }),
...SharedParams,
coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])),
};
@@ -207,6 +209,7 @@ export class BackgroundPass {
}
if (!this.image) return;
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);
@@ -369,6 +372,12 @@ 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];
if (typeof HTMLImageElement === 'undefined') {
console.error(`Missing "HTMLImageElement" required for background skybox`);
onload?.(true);
return { texture: createNullTexture(), assets };
}
const texture = ctx.resources.cubeTexture(cubeFaces, true, onload);
return { texture, assets };
}
@@ -390,18 +399,28 @@ function areImageTexturePropsEqual(sourceA: ImageProps['source'], sourceB: Image
}
function getImageTexture(ctx: WebGLContext, assetManager: AssetManager, source: ImageProps['source'], onload?: (errored?: boolean) => void): { texture: Texture, asset: Asset } {
const asset = source.name === 'url'
? Asset.getUrlAsset(assetManager, source.params)
: source.params!;
if (typeof HTMLImageElement === 'undefined') {
console.error(`Missing "HTMLImageElement" required for background image`);
onload?.(true);
return { texture: createNullTexture(), asset };
}
const texture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
const img = new Image();
img.onload = () => {
texture.load(img);
if (ctx.isWebGL2 || (isPowerOfTwo(img.width) && isPowerOfTwo(img.height))) {
texture.mipmap();
}
onload?.();
};
img.onerror = () => {
onload?.(true);
};
const asset = source.name === 'url'
? Asset.getUrlAsset(assetManager, source.params)
: source.params!;
assetManager.resolve(asset, 'binary').run().then(a => {
const blob = new Blob([a.data]);
img.src = URL.createObjectURL(blob);

View File

@@ -0,0 +1,120 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { DefineSpec, 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 { Vec2 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { Viewport } from '../camera/util';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { isTimingMode } from '../../mol-util/debug';
import { cas_frag } from '../../mol-gl/shader/cas.frag';
export const CasParams = {
sharpness: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }),
denoise: PD.Boolean(true),
};
export type CasProps = PD.Values<typeof CasParams>
export class CasPass {
private readonly renderable: CasRenderable;
constructor(private webgl: WebGLContext, input: Texture) {
this.renderable = getCasRenderable(webgl, input);
}
private updateState(viewport: Viewport) {
const { gl, state } = this.webgl;
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
const { x, y, width, height } = viewport;
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
setSize(width: number, height: number) {
ValueCell.update(this.renderable.values.uTexSizeInv, Vec2.set(this.renderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
}
update(input: Texture, props: CasProps) {
const { values } = this.renderable;
const { sharpness, denoise } = props;
let needsUpdate = false;
if (values.tColor.ref.value !== input) {
ValueCell.update(this.renderable.values.tColor, input);
needsUpdate = true;
}
ValueCell.updateIfChanged(values.uSharpness, 2 - 2 * Math.pow(sharpness, 0.25));
if (values.dDenoise.ref.value !== denoise) needsUpdate = true;
ValueCell.updateIfChanged(values.dDenoise, denoise);
if (needsUpdate) {
this.renderable.update();
}
}
render(viewport: Viewport, target: RenderTarget | undefined) {
if (isTimingMode) this.webgl.timer.mark('CasPass.render');
if (target) {
target.bind();
} else {
this.webgl.unbindFramebuffer();
}
this.updateState(viewport);
this.renderable.render();
if (isTimingMode) this.webgl.timer.markEnd('CasPass.render');
}
}
//
const CasSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
uSharpness: UniformSpec('f'),
dDenoise: DefineSpec('boolean'),
};
const CasShaderCode = ShaderCode('cas', quad_vert, cas_frag);
type CasRenderable = ComputeRenderable<Values<typeof CasSchema>>
function getCasRenderable(ctx: WebGLContext, colorTexture: Texture): CasRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof CasSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
uSharpness: ValueCell.create(0.5),
dDenoise: ValueCell.create(true),
};
const schema = { ...CasSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CasShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

View File

@@ -115,19 +115,19 @@ export class DrawPass {
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
if (this.wboit?.supported) {
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);
}
if (this.wboit?.supported) {
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) {

View File

@@ -53,6 +53,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
export const MultiSampleParams = {
mode: PD.Select('temporal', [['off', 'Off'], ['on', 'On'], ['temporal', 'Temporal']]),
sampleLevel: PD.Numeric(2, { min: 0, max: 5, step: 1 }, { description: 'Take level^2 samples.' }),
reduceFlicker: PD.Boolean(true, { description: 'Reduce flicker in "temporal" mode.' }),
};
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>

View File

@@ -33,6 +33,7 @@ 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';
import { CasParams, CasPass } from './cas';
const OutlinesSchema = {
...QuadSchema,
@@ -399,6 +400,10 @@ export const PostprocessingParams = {
smaa: PD.Group(SmaaParams),
off: PD.Group({})
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
sharpening: PD.MappedStatic('off', {
on: PD.Group(CasParams),
off: PD.Group({})
}, { cycle: true, description: 'Contrast Adaptive Sharpening' }),
background: PD.Group(BackgroundParams, { isFlat: true }),
};
@@ -464,14 +469,13 @@ export class PostprocessingPass {
private nSamples: number;
private blurKernelSize: number;
private downsampleFactor: number;
private readonly renderable: PostprocessingRenderable;
private ssaoScale: number;
private calcSsaoScale() {
private calcSsaoScale(resolutionScale: number) {
// downscale ssao for high pixel-ratios
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
return Math.min(1, 1 / this.webgl.pixelRatio) * resolutionScale;
}
private levels: { radius: number, bias: number }[];
@@ -486,8 +490,7 @@ export class PostprocessingPass {
this.nSamples = 1;
this.blurKernelSize = 1;
this.downsampleFactor = 1;
this.ssaoScale = this.calcSsaoScale();
this.ssaoScale = this.calcSsaoScale(1);
this.levels = [];
// needs to be linear for anti-aliasing pass
@@ -517,10 +520,12 @@ export class PostprocessingPass {
: webgl.createRenderTarget(sw, sh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
const depthTexture = this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture;
this.depthHalfTarget = drawPass.packedDepth
? webgl.createRenderTarget(hw, hh, false, 'uint8', 'linear', 'rgba')
: webgl.createRenderTarget(hw, hh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthHalfRenderable = createCopyRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
this.depthHalfRenderable = createCopyRenderable(webgl, depthTexture);
this.depthQuarterTarget = drawPass.packedDepth
? webgl.createRenderTarget(qw, qh, false, 'uint8', 'linear', 'rgba')
@@ -537,7 +542,7 @@ export class PostprocessingPass {
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
this.ssaoRenderable = getSsaoRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture, this.depthHalfTarget.texture, this.depthQuarterTarget.texture);
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture, this.depthHalfTarget.texture, this.depthQuarterTarget.texture);
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
@@ -547,7 +552,7 @@ export class PostprocessingPass {
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
const ssaoScale = this.calcSsaoScale();
const ssaoScale = this.calcSsaoScale(1);
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
this.ssaoScale = ssaoScale;
@@ -580,6 +585,13 @@ export class PostprocessingPass {
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
const depthTexture = this.ssaoScale === 1 ? this.drawPass.depthTextureOpaque : this.downsampledDepthTarget.texture;
ValueCell.update(this.depthHalfRenderable.values.tColor, depthTexture);
ValueCell.update(this.ssaoRenderable.values.tDepth, depthTexture);
this.depthHalfRenderable.update();
this.ssaoRenderable.update();
this.background.setSize(width, height);
}
}
@@ -589,6 +601,7 @@ export class PostprocessingPass {
let needsUpdateMain = false;
let needsUpdateSsao = false;
let needsUpdateSsaoBlur = false;
let needsUpdateDepthHalf = false;
let needsUpdateOutlines = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
@@ -678,11 +691,12 @@ export class PostprocessingPass {
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
if (this.downsampleFactor !== props.occlusion.params.resolutionScale) {
const ssaoScale = this.calcSsaoScale(props.occlusion.params.resolutionScale);
if (this.ssaoScale !== ssaoScale) {
needsUpdateSsao = true;
needsUpdateDepthHalf = true;
this.downsampleFactor = props.occlusion.params.resolutionScale;
this.ssaoScale = this.calcSsaoScale();
this.ssaoScale = ssaoScale;
const sw = Math.floor(w * this.ssaoScale);
const sh = Math.floor(h * this.ssaoScale);
@@ -698,11 +712,9 @@ export class PostprocessingPass {
const qh = Math.floor(sh * 0.25);
this.depthQuarterTarget.setSize(qw, qh);
if (this.ssaoScale === 1) {
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureOpaque);
} else {
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
}
const depthTexture = this.ssaoScale === 1 ? this.drawPass.depthTextureOpaque : this.downsampledDepthTarget.texture;
ValueCell.update(this.depthHalfRenderable.values.tColor, depthTexture);
ValueCell.update(this.ssaoRenderable.values.tDepth, depthTexture);
ValueCell.update(this.ssaoRenderable.values.tDepthHalf, this.depthHalfTarget.texture);
ValueCell.update(this.ssaoRenderable.values.tDepthQuarter, this.depthQuarterTarget.texture);
@@ -755,8 +767,8 @@ export class PostprocessingPass {
if (props.outline.name === 'on') {
const transparentOutline = props.outline.params.includeTransparent ?? true;
const outlineScale = props.outline.params.scale - 1;
const outlineThreshold = 50 * props.outline.params.threshold;
const outlineScale = Math.max(1, Math.round(props.outline.params.scale * this.webgl.pixelRatio)) - 1;
const outlineThreshold = 50 * props.outline.params.threshold * this.webgl.pixelRatio;
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
@@ -824,6 +836,10 @@ export class PostprocessingPass {
this.ssaoBlurSecondPassRenderable.update();
}
if (needsUpdateDepthHalf) {
this.depthHalfRenderable.update();
}
if (needsUpdateMain) {
this.renderable.update();
}
@@ -942,8 +958,11 @@ export class AntialiasingPass {
}
readonly target: RenderTarget;
private readonly internalTarget: RenderTarget;
private readonly fxaa: FxaaPass;
private readonly smaa: SmaaPass;
private readonly cas: CasPass;
constructor(webgl: WebGLContext, private drawPass: DrawPass) {
const { colorTarget } = drawPass;
@@ -951,8 +970,11 @@ export class AntialiasingPass {
const height = colorTarget.getHeight();
this.target = webgl.createRenderTarget(width, height, false);
this.internalTarget = webgl.createRenderTarget(width, height, false);
this.fxaa = new FxaaPass(webgl, this.target.texture);
this.smaa = new SmaaPass(webgl, this.target.texture);
this.cas = new CasPass(webgl, this.target.texture);
}
setSize(width: number, height: number) {
@@ -961,41 +983,69 @@ export class AntialiasingPass {
if (width !== w || height !== h) {
this.target.setSize(width, height);
this.internalTarget.setSize(width, height);
this.fxaa.setSize(width, height);
if (this.smaa.supported) this.smaa.setSize(width, height);
this.cas.setSize(width, height);
}
}
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
private _renderFxaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.antialiasing.name !== 'fxaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.fxaa.update(input, props.antialiasing.params);
this.fxaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
this.fxaa.render(camera.viewport, target);
}
private _renderSmaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
private _renderSmaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.antialiasing.name !== 'smaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.smaa.update(input, props.antialiasing.params);
this.smaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
this.smaa.render(camera.viewport, target);
}
private _renderAntialiasing(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.antialiasing.name === 'fxaa') {
this._renderFxaa(camera, target, props);
} else if (props.antialiasing.name === 'smaa') {
this._renderSmaa(camera, target, props);
}
}
private _renderCas(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.sharpening.name !== 'on') return;
const input = props.antialiasing.name !== 'off'
? this.internalTarget.texture
: PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.cas.update(input, props.sharpening.params);
this.cas.render(camera.viewport, target);
}
render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name === 'off') return;
if (props.antialiasing.name === 'off' && props.sharpening.name === 'off') return;
if (props.antialiasing.name === 'fxaa') {
this._renderFxaa(camera, toDrawingBuffer, props);
} else if (props.antialiasing.name === 'smaa') {
if (!this.smaa.supported) {
throw new Error('SMAA not supported, missing "HTMLImageElement"');
}
this._renderSmaa(camera, toDrawingBuffer, props);
if (props.antialiasing.name === 'smaa' && !this.smaa.supported) {
console.error('SMAA not supported, missing "HTMLImageElement"');
return;
}
const target = toDrawingBuffer ? undefined : this.target;
if (props.sharpening.name === 'off') {
this._renderAntialiasing(camera, target, props);
} else if (props.antialiasing.name === 'off') {
this._renderCas(camera, target, props);
} else {
this._renderAntialiasing(camera, this.internalTarget, props);
this._renderCas(camera, target, props);
}
}
}

View File

@@ -93,7 +93,7 @@ export namespace BaseGeometry {
if (!transform) transform = createIdentityTransform();
const locationIterator = LocationIterator(1, transform.instanceCount.ref.value, 1, () => NullLocation, false, () => false);
const theme: Theme = {
color: UniformColorTheme({}, { value: colorValue }),
color: UniformColorTheme({}, { value: colorValue, lightness: 0, saturation: 0 }),
size: UniformSizeTheme({}, { value: sizeValue })
};
return { transform, locationIterator, theme };

View File

@@ -156,7 +156,7 @@ export namespace Cylinders {
sizeAspectRatio: PD.Numeric(1, { min: 0, max: 3, step: 0.01 }),
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], 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),
@@ -244,7 +244,7 @@ export namespace Cylinders {
uSizeFactor: ValueCell.create(props.sizeFactor * props.sizeAspectRatio),
uDoubleSided: ValueCell.create(props.doubleSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
dSolidInterior: ValueCell.create(props.solidInterior),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
@@ -263,7 +263,7 @@ export namespace Cylinders {
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor * props.sizeAspectRatio);
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);

View File

@@ -147,7 +147,7 @@ export namespace DirectVolume {
export const Params = {
...BaseGeometry.Params,
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
controlPoints: PD.LineGraph([
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
@@ -269,7 +269,7 @@ export namespace DirectVolume {
dAxisOrder: ValueCell.create(directVolume.axisOrder.ref.value.join('')),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
};
}
@@ -282,7 +282,7 @@ export namespace DirectVolume {
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
BaseGeometry.updateValues(values, props);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
createTransferFunctionTexture(controlPoints, values.tTransferTex);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -81,7 +81,7 @@ export namespace Geometry {
switch (geometry.kind) {
case 'mesh': return geometry.vertexCount;
case 'points': return geometry.pointCount;
case 'spheres': return geometry.sphereCount * 4;
case 'spheres': return geometry.sphereCount * 6;
case 'cylinders': return geometry.cylinderCount * 6;
case 'text': return geometry.charCount * 4;
case 'lines': return geometry.lineCount * 4;

View File

@@ -627,7 +627,7 @@ export namespace Mesh {
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
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),
@@ -706,7 +706,7 @@ export namespace Mesh {
dFlatShaded: ValueCell.create(props.flatShaded),
dFlipSided: ValueCell.create(props.flipSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
@@ -727,7 +727,7 @@ export namespace Mesh {
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -7,21 +7,8 @@
import { ChunkedArray } from '../../../mol-data/util';
import { Spheres } from './spheres';
const quadMapping = new Float32Array([
-1.0, 1.0,
-1.0, -1.0,
1.0, 1.0,
1.0, -1.0
]);
const quadIndices = new Uint16Array([
0, 1, 2,
1, 3, 2
]);
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const caAdd3 = ChunkedArray.add3;
const caAdd2 = ChunkedArray.add2;
const caAdd = ChunkedArray.add;
export interface SpheresBuilder {
@@ -31,30 +18,18 @@ export interface SpheresBuilder {
export namespace SpheresBuilder {
export function create(initialCount = 2048, chunkSize = 1024, spheres?: Spheres): SpheresBuilder {
initialCount *= 4;
chunkSize *= 4;
const centers = ChunkedArray.create(Float32Array, 3, chunkSize, spheres ? spheres.centerBuffer.ref.value : initialCount);
const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, spheres ? spheres.mappingBuffer.ref.value : initialCount);
const indices = ChunkedArray.create(Uint32Array, 3, chunkSize / 2, spheres ? spheres.indexBuffer.ref.value : initialCount / 2);
const groups = ChunkedArray.create(Float32Array, 1, chunkSize, spheres ? spheres.groupBuffer.ref.value : initialCount);
return {
add: (x: number, y: number, z: number, group: number) => {
const offset = centers.elementCount;
for (let i = 0; i < 4; ++i) {
caAdd3(centers, x, y, z);
caAdd2(mappings, quadMapping[i * 2], quadMapping[i * 2 + 1]);
caAdd(groups, group);
}
caAdd3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]);
caAdd3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]);
caAdd3(centers, x, y, z);
caAdd(groups, group);
},
getSpheres: () => {
const cb = ChunkedArray.compact(centers, true) as Float32Array;
const mb = ChunkedArray.compact(mappings, true) as Float32Array;
const ib = ChunkedArray.compact(indices, true) as Uint32Array;
const gb = ChunkedArray.compact(groups, true) as Float32Array;
return Spheres.create(cb, mb, ib, gb, centers.elementCount / 4, spheres);
return Spheres.create(cb, gb, centers.elementCount, spheres);
}
};
}

View File

@@ -13,7 +13,7 @@ import { Theme } from '../../../mol-theme/theme';
import { SpheresValues } from '../../../mol-gl/renderable/spheres';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { TextureImage, calculateInvariantBoundingSphere, calculateTransformBoundingSphere, createTextureImage } from '../../../mol-gl/renderable/util';
import { Sphere3D } from '../../../mol-math/geometry';
import { createSizes, getMaxSize } from '../size-data';
import { Color } from '../../../mol-util/color';
@@ -23,7 +23,7 @@ import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { GroupMapping, createGroupMapping } from '../../util';
import { createEmptyClipping } from '../clipping-data';
import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
@@ -35,10 +35,6 @@ export interface Spheres {
/** Center buffer as array of xyz values wrapped in a value cell */
readonly centerBuffer: ValueCell<Float32Array>,
/** Mapping buffer as array of xy values wrapped in a value cell */
readonly mappingBuffer: ValueCell<Float32Array>,
/** Index buffer as array of center index triplets wrapped in a value cell */
readonly indexBuffer: ValueCell<Uint32Array>,
/** Group buffer as array of group ids for each vertex wrapped in a value cell */
readonly groupBuffer: ValueCell<Float32Array>,
@@ -48,45 +44,51 @@ export interface Spheres {
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
shaderData: Spheres.ShaderData
}
export namespace Spheres {
export function create(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
export interface ShaderData {
readonly positionGroup: ValueCell<TextureImage<Float32Array>>
readonly texDim: ValueCell<Vec2>
update(): void
}
export function create(centers: Float32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
return spheres ?
update(centers, mappings, indices, groups, sphereCount, spheres) :
fromArrays(centers, mappings, indices, groups, sphereCount);
update(centers, groups, sphereCount, spheres) :
fromArrays(centers, groups, sphereCount);
}
export function createEmpty(spheres?: Spheres): Spheres {
const cb = spheres ? spheres.centerBuffer.ref.value : new Float32Array(0);
const mb = spheres ? spheres.mappingBuffer.ref.value : new Float32Array(0);
const ib = spheres ? spheres.indexBuffer.ref.value : new Uint32Array(0);
const gb = spheres ? spheres.groupBuffer.ref.value : new Float32Array(0);
return create(cb, mb, ib, gb, 0, spheres);
return create(cb, gb, 0, spheres);
}
function hashCode(spheres: Spheres) {
return hashFnv32a([
spheres.sphereCount,
spheres.centerBuffer.ref.version, spheres.mappingBuffer.ref.version,
spheres.indexBuffer.ref.version, spheres.groupBuffer.ref.version
spheres.centerBuffer.ref.version,
spheres.groupBuffer.ref.version
]);
}
function fromArrays(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number): Spheres {
function fromArrays(centers: Float32Array, groups: Float32Array, sphereCount: number): Spheres {
const boundingSphere = Sphere3D();
let groupMapping: GroupMapping;
let currentHash = -1;
let currentGroup = -1;
const positionGroup = ValueCell.create(createTextureImage(1, 4, Float32Array));
const texDim = ValueCell.create(Vec2.create(0, 0));
const spheres = {
kind: 'spheres' as const,
sphereCount,
centerBuffer: ValueCell.create(centers),
mappingBuffer: ValueCell.create(mappings),
indexBuffer: ValueCell.create(indices),
groupBuffer: ValueCell.create(groups),
get boundingSphere() {
const newHash = hashCode(spheres);
@@ -107,30 +109,49 @@ export namespace Spheres {
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere);
currentHash = hashCode(spheres);
}
},
shaderData: {
positionGroup,
texDim,
update() {
const pgt = createTextureImage(spheres.sphereCount, 4, Float32Array, positionGroup.ref.value.array);
setPositionGroup(pgt, spheres.centerBuffer.ref.value, spheres.groupBuffer.ref.value, spheres.sphereCount);
ValueCell.update(positionGroup, pgt);
ValueCell.update(texDim, Vec2.set(texDim.ref.value, pgt.width, pgt.height));
}
},
};
return spheres;
}
function update(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
if (sphereCount > spheres.sphereCount) {
ValueCell.update(spheres.mappingBuffer, mappings);
ValueCell.update(spheres.indexBuffer, indices);
}
function update(centers: Float32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
spheres.sphereCount = sphereCount;
ValueCell.update(spheres.centerBuffer, centers);
ValueCell.update(spheres.groupBuffer, groups);
spheres.shaderData.update();
return spheres;
}
function setPositionGroup(out: TextureImage<Float32Array>, centers: Float32Array, groups: Float32Array, count: number) {
const { array } = out;
for (let i = 0; i < count; ++i) {
array[i * 4 + 0] = centers[i * 3 + 0];
array[i * 4 + 1] = centers[i * 3 + 1];
array[i * 4 + 2] = centers[i * 3 + 2];
array[i * 4 + 3] = groups[i];
}
}
export const Params = {
...BaseGeometry.Params,
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
approximate: PD.Boolean(false, { ...BaseGeometry.ShadingCategory, description: 'Faster rendering, but has artifacts.' }),
alphaThickness: PD.Numeric(0, { min: 0, max: 20, step: 1 }, { ...BaseGeometry.ShadingCategory, description: 'If not zero, adjusts alpha for radius.' }),
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),
};
@@ -149,7 +170,7 @@ export namespace Spheres {
};
function createPositionIterator(spheres: Spheres, transform: TransformData): LocationIterator {
const groupCount = spheres.sphereCount * 4;
const groupCount = spheres.sphereCount;
const instanceCount = transform.instanceCount.ref.value;
const location = PositionLocation();
const p = location.position;
@@ -163,7 +184,7 @@ export namespace Spheres {
}
return location;
};
return LocationIterator(groupCount, instanceCount, 4, getLocation);
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
function createValues(spheres: Spheres, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): SpheresValues {
@@ -180,19 +201,20 @@ export namespace Spheres {
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 6, groupCount, instanceCount };
const padding = spheres.boundingSphere.radius ? getMaxSize(size) * props.sizeFactor : 0;
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
spheres.shaderData.update();
return {
dGeometryType: ValueCell.create('spheres'),
aPosition: spheres.centerBuffer,
aMapping: spheres.mappingBuffer,
aGroup: spheres.groupBuffer,
elements: spheres.indexBuffer,
uTexDim: spheres.shaderData.texDim,
tPositionGroup: spheres.shaderData.positionGroup,
boundingSphere: ValueCell.create(boundingSphere),
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
@@ -211,11 +233,16 @@ export namespace Spheres {
uSizeFactor: ValueCell.create(props.sizeFactor),
uDoubleSided: ValueCell.create(props.doubleSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
dSolidInterior: ValueCell.create(props.solidInterior),
dApproximate: ValueCell.create(props.approximate),
uAlphaThickness: ValueCell.create(props.alphaThickness),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
centerBuffer: spheres.centerBuffer,
groupBuffer: spheres.groupBuffer,
};
}
@@ -230,9 +257,11 @@ export namespace Spheres {
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor);
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
ValueCell.updateIfChanged(values.dApproximate, props.approximate);
ValueCell.updateIfChanged(values.uAlphaThickness, props.alphaThickness);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
}

View File

@@ -119,7 +119,7 @@ export namespace TextureMesh {
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
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),
@@ -215,7 +215,7 @@ export namespace TextureMesh {
dFlatShaded: ValueCell.create(props.flatShaded),
dFlipSided: ValueCell.create(props.flipSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
@@ -236,7 +236,7 @@ export namespace TextureMesh {
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);

View File

@@ -762,6 +762,7 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
isContextLost: function () { return false; },
drawingBufferWidth: width,
drawingBufferHeight: height,
drawingBufferColorSpace: 'srgb',
blendColor: function () { },
blendEquation: function () { },
blendEquationSeparate: function () { },

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -17,10 +17,11 @@ export function getGLContext(width: number, height: number) {
return createContext(gl);
}
export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean }) {
export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean, textureFloat?: boolean }) {
try {
const ctx = getGLContext(width, height);
if (requiredExtensions?.fragDepth && !ctx.extensions.fragDepth) return;
if (requiredExtensions?.textureFloat && !ctx.extensions.textureFloat) return;
return ctx;
} catch (e) {
return;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -21,7 +21,7 @@ export function createSpheres() {
}
describe('spheres', () => {
const ctx = tryGetGLContext(32, 32, { fragDepth: true });
const ctx = tryGetGLContext(32, 32, { fragDepth: true, textureFloat: true });
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -25,7 +25,7 @@ export const CylindersSchema = {
padding: ValueSpec('number'),
uDoubleSided: UniformSpec('b', 'material'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
dSolidInterior: DefineSpec('boolean'),
uBumpFrequency: UniformSpec('f', 'material'),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -40,7 +40,7 @@ export const DirectVolumeSchema = {
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
};
export type DirectVolumeSchema = typeof DirectVolumeSchema
export type DirectVolumeValues = Values<DirectVolumeSchema>

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -22,7 +22,7 @@ export const MeshSchema = {
uDoubleSided: UniformSpec('b', 'material'),
dFlipSided: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),

View File

@@ -136,6 +136,7 @@ export const GlobalUniformSchema = {
uCameraDir: UniformSpec('v3'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uFog: UniformSpec('b'),
uFogNear: UniformSpec('f'),
uFogFar: UniformSpec('f'),
uFogColor: UniformSpec('v3'),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -7,26 +7,30 @@
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem, GraphicsRenderVariant } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec } from './schema';
import { GlobalUniformSchema, BaseSchema, Values, InternalSchema, SizeSchema, InternalValues, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec, TextureSpec } from './schema';
import { SpheresShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
export const SpheresSchema = {
...BaseSchema,
...SizeSchema,
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
aMapping: AttributeSpec('float32', 2, 0),
elements: ElementsSpec('uint32'),
uTexDim: UniformSpec('v2'),
tPositionGroup: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
padding: ValueSpec('number'),
uDoubleSided: UniformSpec('b', 'material'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
dSolidInterior: DefineSpec('boolean'),
dApproximate: DefineSpec('boolean'),
uAlphaThickness: UniformSpec('f'),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),
centerBuffer: ValueSpec('float32'),
groupBuffer: ValueSpec('float32'),
};
export type SpheresSchema = typeof SpheresSchema
export type SpheresValues = Values<SpheresSchema>

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -22,7 +22,7 @@ export const TextureMeshSchema = {
uDoubleSided: UniformSpec('b', 'material'),
dFlipSided: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),

View File

@@ -49,23 +49,29 @@ const DefaultPrintImageOptions = {
pixelated: false,
id: 'molstar.debug.image',
normalize: false,
useCanvas: false,
};
export type PrintImageOptions = typeof DefaultPrintImageOptions
export function printTextureImage(textureImage: TextureImage<any>, options: Partial<PrintImageOptions> = {}) {
const { array, width, height } = textureImage;
const itemSize = array.length / (width * height);
const data = new Uint8ClampedArray(width * height * 4);
const [min, max] = arrayMinMax(array);
if (itemSize === 1) {
data.fill(255);
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
data[(y * width + x) * 4 + 3] = array[y * width + x];
const i = y * width + x;
if (options.normalize) {
data[i * 4 + 0] = ((array[i] - min) / (max - min)) * 255;
} else {
data[i * 4 + 0] = array[i] * 255;
}
}
}
} else if (itemSize === 4) {
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;
@@ -87,14 +93,6 @@ let tmpContainer: HTMLDivElement;
export function printImageData(imageData: ImageData, options: Partial<PrintImageOptions> = {}) {
const o = { ...DefaultPrintImageOptions, ...options };
const canvas = tmpCanvas || document.createElement('canvas');
tmpCanvas = canvas;
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = tmpCanvasCtx || canvas.getContext('2d');
tmpCanvasCtx = ctx;
if (!ctx) throw new Error('Could not create canvas 2d context');
ctx.putImageData(imageData, 0, 0);
if (!tmpContainer) {
tmpContainer = document.createElement('div');
@@ -106,23 +104,52 @@ export function printImageData(imageData: ImageData, options: Partial<PrintImage
document.body.appendChild(tmpContainer);
}
canvas.toBlob(imgBlob => {
const objectURL = URL.createObjectURL(imgBlob!);
const existingImg = document.getElementById(o.id) as HTMLImageElement;
const img = existingImg || document.createElement('img');
img.id = o.id;
img.src = objectURL;
img.style.width = imageData.width * o.scale + 'px';
img.style.height = imageData.height * o.scale + 'px';
if (o.useCanvas) {
const existingCanvas = document.getElementById(o.id) as HTMLCanvasElement;
const outCanvas = existingCanvas || document.createElement('canvas');
outCanvas.width = imageData.width;
outCanvas.height = imageData.height;
const outCtx = outCanvas.getContext('2d');
if (!outCtx) throw new Error('Could not create canvas 2d context');
outCtx.putImageData(imageData, 0, 0);
outCanvas.id = o.id;
outCanvas.style.width = imageData.width * o.scale + 'px';
outCanvas.style.height = imageData.height * o.scale + 'px';
if (o.pixelated) {
// not supported in Firefox and IE
img.style.imageRendering = 'pixelated';
outCanvas.style.imageRendering = 'pixelated';
}
img.style.position = 'relative';
img.style.border = 'solid grey';
img.style.pointerEvents = 'none';
if (!existingImg) tmpContainer.appendChild(img);
}, 'image/png');
outCanvas.style.position = 'relative';
outCanvas.style.border = 'solid grey';
outCanvas.style.pointerEvents = 'none';
if (!existingCanvas) tmpContainer.appendChild(outCanvas);
} else {
const canvas = tmpCanvas || document.createElement('canvas');
tmpCanvas = canvas;
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = tmpCanvasCtx || canvas.getContext('2d');
tmpCanvasCtx = ctx;
if (!ctx) throw new Error('Could not create canvas 2d context');
ctx.putImageData(imageData, 0, 0);
canvas.toBlob(imgBlob => {
const objectURL = URL.createObjectURL(imgBlob!);
const existingImg = document.getElementById(o.id) as HTMLImageElement;
const img = existingImg || document.createElement('img');
img.id = o.id;
img.src = objectURL;
img.style.width = imageData.width * o.scale + 'px';
img.style.height = imageData.height * o.scale + 'px';
if (o.pixelated) {
// not supported in Firefox and IE
img.style.imageRendering = 'pixelated';
}
img.style.position = 'relative';
img.style.border = 'solid grey';
img.style.pointerEvents = 'none';
if (!existingImg) tmpContainer.appendChild(img);
}, 'image/png');
}
}
//

View File

@@ -213,6 +213,7 @@ namespace Renderer {
uCameraDir: ValueCell.create(cameraDir),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
uFog: ValueCell.create(true),
uFogNear: ValueCell.create(1),
uFogFar: ValueCell.create(10000),
uFogColor: ValueCell.create(bgColor),
@@ -425,7 +426,8 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) {
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && !xrayShaded) {
renderObject(r, 'depth', Flag.None);
}
}
@@ -443,7 +445,8 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) {
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || xrayShaded) {
renderObject(r, 'depth', Flag.None);
}
}
@@ -593,7 +596,8 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !xrayShaded) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
renderObject(r, 'colorWboit', Flag.None);
}
}
@@ -611,7 +615,8 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value) {
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || xrayShaded) {
renderObject(r, 'colorWboit', Flag.None);
}
}
@@ -633,7 +638,8 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !xrayShaded) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
renderObject(r, 'colorDpoit', Flag.None);
}
}
@@ -659,7 +665,8 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value) {
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || xrayShaded) {
renderObject(r, 'colorDpoit', Flag.None);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -8,6 +8,7 @@ import { ValueCell } from '../mol-util';
import { idFactory } from '../mol-util/id-factory';
import { WebGLExtensions } from './webgl/extensions';
import { isWebGL2, GLRenderingContext } from './webgl/compat';
import { assertUnreachable } from '../mol-util/type-helpers';
export type DefineKind = 'boolean' | 'string' | 'number'
export type DefineType = boolean | string
@@ -20,6 +21,8 @@ export interface ShaderExtensions {
readonly fragDepth?: ShaderExtensionsValue
readonly drawBuffers?: ShaderExtensionsValue
readonly shaderTextureLod?: ShaderExtensionsValue
readonly clipCullDistance?: ShaderExtensionsValue
readonly conservativeDepth?: ShaderExtensionsValue
}
type FragOutTypes = { [k in number]: 'vec4' | 'ivec4' }
@@ -127,7 +130,7 @@ function unrollLoops(str: string) {
return str.replace(reUnrollLoop, loopReplacer);
}
function loopReplacer(match: string, start: string, end: string, snippet: string) {
function loopReplacer(_match: string, start: string, end: string, snippet: string) {
let out = '';
for (let i = parseInt(start); i < parseInt(end); ++i) {
out += snippet
@@ -161,9 +164,10 @@ function ignoreDefine(name: string, variant: string, defines: ShaderDefines): bo
} else {
return [
'dColorType', 'dUsePalette',
'dLightCount',
'dLightCount', 'dXrayShaded',
'dOverpaintType', 'dOverpaint',
'dSubstanceType', 'dSubstance',
'dColorMarker',
].includes(name);
}
return false;
@@ -204,7 +208,6 @@ export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_v
import { image_vert } from './shader/image.vert';
import { image_frag } from './shader/image.frag';
import { assertUnreachable } from '../mol-util/type-helpers';
export const ImageShaderCode = ShaderCode('image', image_vert, image_frag, { drawBuffers: 'optional' }, {}, ignoreDefineUnlit);
//
@@ -313,6 +316,22 @@ function getGlsl300VertPrefix(extensions: WebGLExtensions, shaderExtensions: Sha
prefix.push('#define requiredDrawBuffers');
}
}
if (shaderExtensions.clipCullDistance) {
if (extensions.clipCullDistance) {
prefix.push('#extension GL_ANGLE_clip_cull_distance : enable');
prefix.push('#define enabledClipCullDistance');
} else if (shaderExtensions.clipCullDistance === 'required') {
throw new Error(`required 'GL_ANGLE_clip_cull_distance' extension not available`);
}
}
if (shaderExtensions.conservativeDepth) {
if (extensions.conservativeDepth) {
prefix.push('#extension GL_EXT_conservative_depth : enable');
prefix.push('#define enabledConservativeDepth');
} else if (shaderExtensions.conservativeDepth === 'required') {
throw new Error(`required 'GL_EXT_conservative_depth' extension not available`);
}
}
if (extensions.noNonInstancedActiveAttribs) {
prefix.push('#define noNonInstancedActiveAttribs');
}

View File

@@ -14,6 +14,7 @@ precision mediump sampler2D;
uniform sampler2D tImage;
uniform vec2 uImageScale;
uniform vec2 uImageOffset;
uniform float uBlur;
uniform float uOpacity;
uniform float uSaturation;
uniform float uLightness;
@@ -64,7 +65,11 @@ void main() {
} else {
coords = (gl_FragCoord.xy / uImageScale) + uImageOffset;
}
gl_FragColor = texture2D(tImage, vec2(coords.x, 1.0 - coords.y));
#ifdef enabledShaderTextureLod
gl_FragColor = texture2DLodEXT(tImage, vec2(coords.x, 1.0 - coords.y), uBlur * 8.0);
#else
gl_FragColor = texture2D(tImage, vec2(coords.x, 1.0 - coords.y));
#endif
gl_FragColor.a = uOpacity;
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);
#elif defined(dVariant_horizontalGradient)

View File

@@ -0,0 +1,145 @@
export const cas_frag = `
precision mediump float;
precision mediump sampler2D;
uniform sampler2D tColor;
uniform vec2 uTexSizeInv;
uniform float uSharpness;
// adapted from https://www.shadertoy.com/view/stXSWB
/*
* FidelityFX Super Resolution scales up a low resolution
* image, while adding fine detail.
*
* MIT Open License
*
* https://gpuopen.com/fsr
*
* Left: FSR processed
* Right: Original texture, bilinear interpolation
*
* Mouse at top: Sharpness 0 stops (maximum)
* Mouse at bottom: Sharpness 2 stops (minimum)
*
* It works in two passes-
* EASU upsamples the image with a clamped Lanczos kernel.
* RCAS sharpens the image at the target resolution.
*
* I needed to make a few changes to improve readability and
* WebGL compatibility in an algorithm I don't fully understand.
* Expect bugs.
*
* Shader not currently running for WebGL1 targets (eg. mobile Safari)
*
* There is kind of no point to using FSR in Shadertoy, as it renders buffers
* at full target resolution. But this might be useful for WebGL based demos
* running smaller-than-target render buffers.
*
* For sharpening with a full resolution render buffer,
* FidelityFX CAS is a better option.
* https://www.shadertoy.com/view/ftsXzM
*
* For readability and compatibility, these optimisations have been removed:
* * Fast approximate inverse and inversesqrt
* * textureGather fetches (not WebGL compatible)
* * Multiplying by reciprocal instead of division
*
* Apologies to AMD for the numerous slowdowns and errors I have introduced.
*
*/
/***** RCAS *****/
#define FSR_RCAS_LIMIT (0.25-(1.0/16.0))
// Input callback prototypes that need to be implemented by calling shader
vec4 FsrRcasLoadF(vec2 p);
//------------------------------------------------------------------------------------------------------------------------------
void FsrRcasCon(
out float con,
// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}.
float sharpness
) {
// Transform from stops to linear value.
con = exp2(-sharpness);
}
vec3 FsrRcasF(
vec2 ip, // Integer pixel position in output.
float con
) {
// Constant generated by RcasSetup().
// Algorithm uses minimal 3x3 pixel neighborhood.
// b
// d e f
// h
vec2 sp = vec2(ip);
vec3 b = FsrRcasLoadF(sp + vec2( 0,-1)).rgb;
vec3 d = FsrRcasLoadF(sp + vec2(-1, 0)).rgb;
vec3 e = FsrRcasLoadF(sp).rgb;
vec3 f = FsrRcasLoadF(sp + vec2( 1, 0)).rgb;
vec3 h = FsrRcasLoadF(sp + vec2( 0, 1)).rgb;
// Luma times 2.
float bL = b.g + .5 * (b.b + b.r);
float dL = d.g + .5 * (d.b + d.r);
float eL = e.g + .5 * (e.b + e.r);
float fL = f.g + .5 * (f.b + f.r);
float hL = h.g + .5 * (h.b + h.r);
// Noise detection.
#ifdef dDenoise
float nz = .25 * (bL + dL + fL + hL) - eL;
nz=clamp(
abs(nz)
/(
max(max(bL,dL),max(eL,max(fL,hL)))
-min(min(bL,dL),min(eL,min(fL,hL)))
),
0., 1.
);
nz=1.-.5*nz;
#endif
// Min and max of ring.
vec3 mn4 = min(b, min(f, h));
vec3 mx4 = max(b, max(f, h));
// Immediate constants for peak range.
vec2 peakC = vec2(1., -4.);
// Limiters, these need to be high precision RCPs.
vec3 hitMin = mn4 / (4. * mx4);
vec3 hitMax = (peakC.x - mx4) / (4.* mn4 + peakC.y);
vec3 lobeRGB = max(-hitMin, hitMax);
float lobe = max(
-FSR_RCAS_LIMIT,
min(max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)), 0.)
)*con;
// Apply noise removal.
#ifdef dDenoise
lobe *= nz;
#endif
// Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes.
return (lobe * (b + d + h + f) + e) / (4. * lobe + 1.);
}
vec4 FsrRcasLoadF(vec2 p) {
return texture2D(tColor, p * uTexSizeInv);
}
void main() {
// Set up constants
float con;
FsrRcasCon(con, uSharpness);
// Perform RCAS pass
vec3 col = FsrRcasF(gl_FragCoord.xy, con);
gl_FragColor = vec4(col, FsrRcasLoadF(gl_FragCoord.xy).a);
}
`;

View File

@@ -1,30 +1,32 @@
export const apply_fog = `
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
float preFogAlpha = gl_FragColor.a;
if (!uTransparentBackground) {
if (gl_FragColor.a < 1.0) {
// transparent objects are blended with background color
gl_FragColor.a = fogAlpha;
} else {
// mix opaque objects with background color
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
}
} else {
#if defined(dRenderVariant_colorDpoit)
if (uFog) {
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
if (!uTransparentBackground) {
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
// mix opaque objects with background color
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
}
} else {
#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;
}
#else
// pre-multiplied alpha expected for transparent background
gl_FragColor.rgb *= fogAlpha;
gl_FragColor.a = fogAlpha;
#endif
#endif
}
}
`;

View File

@@ -69,8 +69,10 @@ export const apply_light_color = `
gl_FragColor = vec4(outgoingLight, color.a);
#endif
#ifdef dXrayShaded
#if defined(dXrayShaded_on)
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
#elif defined(dXrayShaded_inverted)
gl_FragColor.a *= pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
#endif
gl_FragColor.rgb *= uExposure;

View File

@@ -65,7 +65,7 @@ export const common_clip = `
#if __VERSION__ == 100
// 8-bit
int bitwiseAnd(const in int a, const in int b) {
int bitwiseAnd(in int a, in int b) {
int d = 128;
int result = 0;
for (int i = 0; i < 8; ++i) {

View File

@@ -57,6 +57,7 @@ uniform float uNear;
uniform float uFar;
uniform float uIsOrtho;
uniform bool uFog;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;

View File

@@ -21,6 +21,10 @@ export const common = `
#define dNeedsMarker
#endif
#if defined(dXrayShaded_on) || defined(dXrayShaded_inverted)
#define dXrayShaded
#endif
#define MaskAll 0
#define MaskOpaque 1
#define MaskTransparent 2

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
@@ -11,9 +11,8 @@ export const dpoit_write = `
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)) {
if (preFogAlpha != 1.0 && fragmentDepth < getDepth(coords)) {
#ifdef dTransparentBackfaces_off
if (interior) discard;
#endif

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -12,8 +12,7 @@ export const wboit_write = `
discard;
}
} else if (uRenderMask == MaskTransparent) {
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize) || fragmentDepth > 0.99)) {
if (preFogAlpha != 1.0 && fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
#ifdef dTransparentBackfaces_off
if (interior) discard;
#endif

View File

@@ -68,6 +68,7 @@ uniform int uGroupCount;
uniform float uMetalness;
uniform float uRoughness;
uniform bool uFog;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -17,9 +17,9 @@ precision highp int;
#include common_clip
uniform mat4 uInvView;
uniform float uAlphaThickness;
varying float vRadius;
varying float vRadiusSq;
varying vec3 vPoint;
varying vec3 vPointViewPosition;
@@ -37,7 +37,7 @@ bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal
vec3 cameraSphereDir = mix(cameraSpherePos, rayOrigin - cameraSpherePos, uIsOrtho);
float B = dot(rayDirection, cameraSphereDir);
float det = B * B + vRadiusSq - dot(cameraSphereDir, cameraSphereDir);
float det = B * B + vRadius * vRadius - dot(cameraSphereDir, cameraSphereDir);
if (det < 0.0) return false;
@@ -83,21 +83,34 @@ bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal
}
void main(void){
vec3 modelPos;
vec3 cameraPos;
vec3 cameraNormal;
float fragmentDepth;
bool clipped = false;
bool hit = SphereImpostor(modelPos, cameraPos, cameraNormal, interior, fragmentDepth);
if (!hit) discard;
if (fragmentDepth < 0.0) discard;
if (fragmentDepth > 1.0) discard;
#ifdef dApproximate
vec3 pointDir = -vPointViewPosition - vPoint;
if (dot(pointDir, pointDir) > vRadius * vRadius) discard;
vec3 vViewPosition = -vPointViewPosition;
fragmentDepth = gl_FragCoord.z;
#if !defined(dIgnoreLight) || defined(dXrayShaded)
pointDir.z -= cos(length(pointDir) / vRadius);
cameraNormal = -normalize(pointDir / vRadius);
#endif
interior = false;
#else
vec3 modelPos;
vec3 cameraPos;
bool hit = SphereImpostor(modelPos, cameraPos, cameraNormal, interior, fragmentDepth);
if (!hit) discard;
vec3 vViewPosition = cameraPos;
vec3 vModelPosition = modelPos;
if (fragmentDepth < 0.0) discard;
if (fragmentDepth > 1.0) discard;
gl_FragDepthEXT = fragmentDepth;
gl_FragDepthEXT = fragmentDepth;
vec3 vModelPosition = modelPos;
vec3 vViewPosition = cameraPos;
#endif
#include clip_pixel
#include assign_material_color
@@ -120,6 +133,10 @@ void main(void){
vec3 normal = -cameraNormal;
#include apply_light_color
if (uRenderMask == MaskTransparent && uAlphaThickness > 0.0) {
gl_FragColor.a *= min(1.0, vRadius / uAlphaThickness);
}
#include apply_interior_color
#include apply_marker_color
#include apply_fog

View File

@@ -17,65 +17,63 @@ precision highp int;
uniform mat4 uModelView;
uniform mat4 uInvProjection;
uniform float uIsOrtho;
uniform vec2 uTexDim;
uniform sampler2D tPositionGroup;
attribute vec3 aPosition;
attribute vec2 aMapping;
attribute mat4 aTransform;
attribute float aInstance;
attribute float aGroup;
varying float vRadius;
varying float vRadiusSq;
varying vec3 vPoint;
varying vec3 vPointViewPosition;
#include matrix_scale
const mat4 D = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, -1.0
);
/**
* Compute point size and center using the technique described in:
* "GPU-Based Ray-Casting of Quadratic Surfaces" http://dl.acm.org/citation.cfm?id=2386396
* by Christian Sigg, Tim Weyrich, Mario Botsch, Markus Gross.
* Bounding rectangle of a clipped, perspective-projected 3D Sphere.
* Michael Mara, Morgan McGuire. 2013
*
* Specialization by Arseny Kapoulkine, MIT License Copyright (c) 2018
* https://github.com/zeux/niagara
*/
void quadraticProjection(const in float radius, const in vec3 position){
vec2 xbc, ybc;
void sphereProjection(const in vec3 p, const in float r, const in vec2 mapping) {
vec3 pr = p * r;
float pzr2 = p.z * p.z - r * r;
mat4 T = mat4(
radius, 0.0, 0.0, 0.0,
0.0, radius, 0.0, 0.0,
0.0, 0.0, radius, 0.0,
position.x, position.y, position.z, 1.0
);
float vx = sqrt(p.x * p.x + pzr2);
float minx = ((vx * p.x - pr.z) / (vx * p.z + pr.x)) * uProjection[0][0];
float maxx = ((vx * p.x + pr.z) / (vx * p.z - pr.x)) * uProjection[0][0];
mat4 R = transpose4(uProjection * uModelView * aTransform * T);
float A = dot(R[3], D * R[3]);
float B = -2.0 * dot(R[0], D * R[3]);
float C = dot(R[0], D * R[0]);
xbc[0] = (-B - sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
xbc[1] = (-B + sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
float sx = abs(xbc[0] - xbc[1]) * 0.5;
float vy = sqrt(p.y * p.y + pzr2);
float miny = ((vy * p.y - pr.z) / (vy * p.z + pr.y)) * uProjection[1][1];
float maxy = ((vy * p.y + pr.z) / (vy * p.z - pr.y)) * uProjection[1][1];
A = dot(R[3], D * R[3]);
B = -2.0 * dot(R[1], D * R[3]);
C = dot(R[1], D * R[1]);
ybc[0] = (-B - sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
ybc[1] = (-B + sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
float sy = abs(ybc[0] - ybc[1]) * 0.5;
gl_Position.xy = vec2(0.5 * (xbc.x + xbc.y), 0.5 * (ybc.x + ybc.y));
gl_Position.xy -= aMapping * vec2(sx, sy);
gl_Position.xy = vec2(maxx + minx, maxy + miny) * -0.5;
gl_Position.xy -= mapping * vec2(maxx - minx, maxy - miny) * 0.5;
gl_Position.xy *= gl_Position.w;
}
void main(void){
#include assign_group
vec2 mapping = vec2(1.0, 1.0); // vertices 2 and 5
#if __VERSION__ == 100
int m = imod(VertexID, 6);
#else
int m = VertexID % 6;
#endif
if (m == 0) {
mapping = vec2(-1.0, 1.0);
} else if (m == 1 || m == 3) {
mapping = vec2(-1.0, -1.0);
} else if (m == 4) {
mapping = vec2(1.0, -1.0);
}
vec4 positionGroup = readFromTexture(tPositionGroup, VertexID / 6, uTexDim);
vec3 position = positionGroup.rgb;
float group = positionGroup.a;
#include assign_color_varying
#include assign_marker_varying
#include assign_clipping_varying
@@ -83,13 +81,24 @@ void main(void){
vRadius = size * matrixScale(uModelView);
vec4 position4 = vec4(aPosition, 1.0);
vec4 position4 = vec4(position, 1.0);
vec4 mvPosition = uModelView * aTransform * position4;
gl_Position = uProjection * vec4(mvPosition.xyz, 1.0);
quadraticProjection(size, aPosition);
#ifdef dApproximate
vec4 mvCorner = vec4(mvPosition.xyz, 1.0);
mvCorner.xy += mapping * vRadius;
gl_Position = uProjection * mvCorner;
#else
if (uIsOrtho == 1.0) {
vec4 mvCorner = vec4(mvPosition.xyz, 1.0);
mvCorner.xy += mapping * vRadius;
gl_Position = uProjection * mvCorner;
} else {
gl_Position = uProjection * vec4(mvPosition.xyz, 1.0);
sphereProjection(mvPosition.xyz, vRadius, mapping);
}
#endif
vRadiusSq = vRadius * vRadius;
vec4 vPoint4 = uInvProjection * gl_Position;
vPoint = vPoint4.xyz / vPoint4.w;
vPointViewPosition = -mvPosition.xyz / mvPosition.w;

View File

@@ -114,7 +114,7 @@ void main(void) {
vec2 selfPackedDepth = packUnitIntervalToRG(selfDepth);
if (isBackground(selfDepth)) {
gl_FragColor = vec4(packUnitIntervalToRG(0.0), selfPackedDepth);
gl_FragColor = vec4(packUnitIntervalToRG(1.0), selfPackedDepth);
return;
}

View File

@@ -102,7 +102,7 @@ export interface Buffer {
destroy: () => void
}
function getBuffer(gl: GLRenderingContext) {
export function getBuffer(gl: GLRenderingContext) {
const buffer = gl.createBuffer();
if (buffer === null) {
throw new Error('Could not create WebGL buffer');

View File

@@ -586,6 +586,123 @@ export function getProvokingVertex(gl: GLRenderingContext): COMPAT_provoking_ver
return null;
}
/**
* See https://registry.khronos.org/webgl/extensions/WEBGL_clip_cull_distance/
*/
export interface COMPAT_clip_cull_distance {
readonly MAX_CLIP_DISTANCES: number;
readonly MAX_CULL_DISTANCES: number;
readonly MAX_COMBINED_CLIP_AND_CULL_DISTANCES: number;
readonly CLIP_DISTANCE0: number;
readonly CLIP_DISTANCE1: number;
readonly CLIP_DISTANCE2: number;
readonly CLIP_DISTANCE3: number;
readonly CLIP_DISTANCE4: number;
readonly CLIP_DISTANCE5: number;
readonly CLIP_DISTANCE6: number;
readonly CLIP_DISTANCE7: number;
}
export function getClipCullDistance(gl: GLRenderingContext): COMPAT_clip_cull_distance | null {
if (isWebGL2(gl)) {
const ext = gl.getExtension('WEBGL_clip_cull_distance');
if (ext) {
return {
MAX_CLIP_DISTANCES: ext.MAX_CLIP_DISTANCES_WEBGL,
MAX_CULL_DISTANCES: ext.MAX_CULL_DISTANCES_WEBGL,
MAX_COMBINED_CLIP_AND_CULL_DISTANCES: ext.MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL,
CLIP_DISTANCE0: ext.CLIP_DISTANCE0_WEBGL,
CLIP_DISTANCE1: ext.CLIP_DISTANCE1_WEBGL,
CLIP_DISTANCE2: ext.CLIP_DISTANCE2_WEBGL,
CLIP_DISTANCE3: ext.CLIP_DISTANCE3_WEBGL,
CLIP_DISTANCE4: ext.CLIP_DISTANCE4_WEBGL,
CLIP_DISTANCE5: ext.CLIP_DISTANCE5_WEBGL,
CLIP_DISTANCE6: ext.CLIP_DISTANCE6_WEBGL,
CLIP_DISTANCE7: ext.CLIP_DISTANCE7_WEBGL
};
}
}
return null;
}
/**
* See https://registry.khronos.org/webgl/extensions/EXT_conservative_depth/
*/
export interface COMPAT_conservative_depth {
}
export function getConservativeDepth(gl: GLRenderingContext): COMPAT_conservative_depth | null {
if (isWebGL2(gl)) {
const ext = gl.getExtension('EXT_conservative_depth');
if (ext) {
return {};
}
}
return null;
}
/**
* See https://registry.khronos.org/webgl/extensions/WEBGL_stencil_texturing/
*/
export interface COMPAT_stencil_texturing {
readonly DEPTH_STENCIL_TEXTURE_MODE: number;
readonly STENCIL_INDEX: number;
}
export function getStencilTexturing(gl: GLRenderingContext): COMPAT_stencil_texturing | null {
if (isWebGL2(gl)) {
const ext = gl.getExtension('WEBGL_stencil_texturing');
if (ext) {
return {
DEPTH_STENCIL_TEXTURE_MODE: ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL,
STENCIL_INDEX: ext.STENCIL_INDEX_WEBGL
};
}
}
return null;
}
/**
* See https://registry.khronos.org/webgl/extensions/EXT_clip_control/
*/
export interface COMPAT_clip_control {
readonly LOWER_LEFT: number;
readonly UPPER_LEFT: number;
readonly NEGATIVE_ONE_TO_ONE: number;
readonly ZERO_TO_ONE: number;
readonly CLIP_ORIGIN: number;
readonly CLIP_DEPTH_MODE: number;
/**
* @param origin must be LOWER_LEFT (default) or UPPER_LEFT.
* @param depth must be NEGATIVE_ONE_TO_ONE (default) or ZERO_TO_ONE.
*/
clipControl(origin: number, depth: number): void
}
export function getClipControl(gl: GLRenderingContext): COMPAT_clip_control | null {
const ext = gl.getExtension('EXT_clip_control');
if (ext) {
return {
LOWER_LEFT: ext.LOWER_LEFT_EXT,
UPPER_LEFT: ext.UPPER_LEFT_EXT,
NEGATIVE_ONE_TO_ONE: ext.NEGATIVE_ONE_TO_ONE_EXT,
ZERO_TO_ONE: ext.ZERO_TO_ONE_EXT,
CLIP_ORIGIN: ext.CLIP_ORIGIN_EXT,
CLIP_DEPTH_MODE: ext.CLIP_DEPTH_MODE_EXT,
clipControl: ext.clipControlEXT.bind(ext)
};
}
return null;
}
export function getNoNonInstancedActiveAttribs(gl: GLRenderingContext): boolean {
if (!isWebGL2(gl)) return false;

View File

@@ -52,6 +52,16 @@ export function checkError(gl: GLRenderingContext) {
}
}
export function glEnumToString(gl: GLRenderingContext, value: number) {
const keys: string[] = [];
for (const key in gl) {
if ((gl as any)[key] === value) {
keys.push(key);
}
}
return keys.length ? keys.join(' | ') : `0x${value.toString(16)}`;
}
function unbindResources(gl: GLRenderingContext) {
// bind null to all texture units
const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
@@ -227,7 +237,7 @@ export interface WebGLContext {
export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScale: number }> = {}): WebGLContext {
const extensions = createExtensions(gl);
const state = createState(gl);
const state = createState(gl, extensions);
const stats = createStats();
const resources = createResources(gl, state, stats, extensions);
const timer = createTimer(gl, extensions, stats);

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject, getDisjointTimerQuery, COMPAT_disjoint_timer_query, getNoNonInstancedActiveAttribs, getDrawBuffersIndexed, COMPAT_draw_buffers_indexed, getParallelShaderCompile, COMPAT_parallel_shader_compile, getFboRenderMipmap, COMPAT_fboRenderMipmap, COMPAT_provoking_vertex, getProvokingVertex } from './compat';
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject, getDisjointTimerQuery, COMPAT_disjoint_timer_query, getNoNonInstancedActiveAttribs, getDrawBuffersIndexed, COMPAT_draw_buffers_indexed, getParallelShaderCompile, COMPAT_parallel_shader_compile, getFboRenderMipmap, COMPAT_fboRenderMipmap, COMPAT_provoking_vertex, getProvokingVertex, COMPAT_clip_cull_distance, getClipCullDistance, COMPAT_conservative_depth, getConservativeDepth, COMPAT_stencil_texturing, getStencilTexturing, COMPAT_clip_control, getClipControl } from './compat';
import { isDebugMode } from '../../mol-util/debug';
export type WebGLExtensions = {
@@ -30,6 +30,10 @@ export type WebGLExtensions = {
parallelShaderCompile: COMPAT_parallel_shader_compile | null
fboRenderMipmap: COMPAT_fboRenderMipmap | null
provokingVertex: COMPAT_provoking_vertex | null
clipCullDistance: COMPAT_clip_cull_distance | null
conservativeDepth: COMPAT_conservative_depth | null
stencilTexturing: COMPAT_stencil_texturing | null
clipControl: COMPAT_clip_control | null
noNonInstancedActiveAttribs: boolean
}
@@ -126,6 +130,22 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
if (isDebugMode && provokingVertex === null) {
console.log('Could not find support for "provoking_vertex"');
}
const clipCullDistance = getClipCullDistance(gl);
if (isDebugMode && clipCullDistance === null) {
console.log('Could not find support for "clip_cull_distance"');
}
const conservativeDepth = getConservativeDepth(gl);
if (isDebugMode && conservativeDepth === null) {
console.log('Could not find support for "conservative_depth"');
}
const stencilTexturing = getStencilTexturing(gl);
if (isDebugMode && stencilTexturing === null) {
console.log('Could not find support for "stencil_texturing"');
}
const clipControl = getClipControl(gl);
if (isDebugMode && clipControl === null) {
console.log('Could not find support for "clipControl"');
}
const noNonInstancedActiveAttribs = getNoNonInstancedActiveAttribs(gl);
@@ -152,6 +172,10 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
parallelShaderCompile,
fboRenderMipmap,
provokingVertex,
clipCullDistance,
conservativeDepth,
stencilTexturing,
clipControl,
noNonInstancedActiveAttribs,
};

View File

@@ -1,10 +1,11 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { GLRenderingContext } from './compat';
import { WebGLExtensions } from './extensions';
export type WebGLState = {
currentProgramId: number
@@ -22,6 +23,7 @@ export type WebGLState = {
* - `gl.SAMPLE_COVERAGE`: ANDing the fragment's coverage with the temporary coverage value
* - `gl.SCISSOR_TEST`: scissor test that discards fragments that are outside of the scissor rectangle
* - `gl.STENCIL_TEST`: stencil testing and updates to the stencil buffer
* - `ext.CLIP_DISTANCE[0-7]`: clip distance 0 to 7 (with `ext` being `WEBGL_clip_cull_distance`)
*/
enable: (cap: number) => void
/**
@@ -35,6 +37,7 @@ export type WebGLState = {
* - `gl.SAMPLE_COVERAGE`: ANDing the fragment's coverage with the temporary coverage value
* - `gl.SCISSOR_TEST`: scissor test that discards fragments that are outside of the scissor rectangle
* - `gl.STENCIL_TEST`: stencil testing and updates to the stencil buffer
* - `ext.CLIP_DISTANCE[0-7]`: clip distance 0 to 7 (with `ext` being `WEBGL_clip_cull_distance`)
*/
disable: (cap: number) => void
@@ -85,10 +88,18 @@ export type WebGLState = {
viewport: (x: number, y: number, width: number, height: number) => void
scissor: (x: number, y: number, width: number, height: number) => void
/**
* controls the clipping volume behavior
* @param origin must be `ext.LOWER_LEFT` (default) or `ext.UPPER_LEFT`.
* @param depth must be `ext.NEGATIVE_ONE_TO_ONE` (default) or `ext.ZERO_TO_ONE`.
* with `ext` being `EXT_clip_control`
*/
clipControl?: (origin: number, depth: number) => void
reset: () => void
}
export function createState(gl: GLRenderingContext): WebGLState {
export function createState(gl: GLRenderingContext, e: WebGLExtensions): WebGLState {
let enabledCapabilities: Record<number, boolean> = {};
let currentFrontFace = gl.getParameter(gl.FRONT_FACE);
@@ -128,6 +139,9 @@ export function createState(gl: GLRenderingContext): WebGLState {
let currentViewport: [number, number, number, number] = gl.getParameter(gl.VIEWPORT);
let currentScissor: [number, number, number, number] = gl.getParameter(gl.SCISSOR_BOX);
let currentClipOrigin = e.clipControl ? gl.getParameter(e.clipControl.CLIP_ORIGIN) : -1;
let currentClipDepthMode = e.clipControl ? gl.getParameter(e.clipControl.CLIP_DEPTH_MODE) : -1;
const clearVertexAttribsState = () => {
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
@@ -378,6 +392,14 @@ export function createState(gl: GLRenderingContext): WebGLState {
}
},
clipControl: e.clipControl ? (origin: number, depth: number) => {
if (origin !== currentClipOrigin || depth !== currentClipDepthMode) {
e.clipControl!.clipControl(origin, depth);
currentClipOrigin = origin;
currentClipDepthMode = depth;
}
} : undefined,
reset: () => {
enabledCapabilities = {};
@@ -420,6 +442,9 @@ export function createState(gl: GLRenderingContext): WebGLState {
currentViewport = gl.getParameter(gl.VIEWPORT);
currentScissor = gl.getParameter(gl.SCISSOR_BOX);
currentClipOrigin = e.clipControl ? gl.getParameter(e.clipControl.CLIP_ORIGIN) : -1;
currentClipDepthMode = e.clipControl ? gl.getParameter(e.clipControl.CLIP_DEPTH_MODE) : -1;
}
};
}

View File

@@ -15,6 +15,7 @@ import { isWebGL2, GLRenderingContext } from './compat';
import { isPromiseLike, ValueOf } from '../../mol-util/type-helpers';
import { WebGLExtensions } from './extensions';
import { objectForEach } from '../../mol-util/object';
import { isPowerOfTwo } from '../../mol-math/misc';
const getNextTextureId = idFactory();
@@ -214,6 +215,7 @@ export interface Texture {
* `define` or `load` without `sub` must have been called before.
*/
load: (image: TextureImage<any> | TextureVolume<any> | HTMLImageElement, sub?: boolean) => void
mipmap: () => void
bind: (id: TextureId) => void
unbind: (id: TextureId) => void
/** Use `layer` to attach a z-slice of a 3D texture */
@@ -275,6 +277,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
let width = 0, height = 0, depth = 0;
let loadedData: undefined | TextureImage<any> | TextureVolume<any> | HTMLImageElement;
let hasMipmap = false;
let destroyed = false;
function define(_width: number, _height: number, _depth?: number) {
@@ -337,6 +340,22 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
loadedData = data;
}
function mipmap() {
if (target !== gl.TEXTURE_2D) {
throw new Error('mipmap only supported for 2d textures');
}
if (isWebGL2(gl) || (isPowerOfTwo(width) && isPowerOfTwo(height))) {
gl.bindTexture(target, texture);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.generateMipmap(target);
gl.bindTexture(target, null);
hasMipmap = true;
} else {
throw new Error('mipmap unsupported for non-power-of-two textures and webgl1');
}
}
function attachFramebuffer(framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) {
framebuffer.bind();
if (target === gl.TEXTURE_2D) {
@@ -365,6 +384,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
define,
load,
mipmap,
bind: (id: TextureId) => {
gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(target, texture);
@@ -392,6 +412,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
width = 0, height = 0, depth = 0; // set to zero to trigger resize
define(_width, _height, _depth);
if (loadedData) load(loadedData);
if (hasMipmap) mipmap();
},
destroy: () => {
if (destroyed) return;
@@ -498,8 +519,8 @@ export function createCubeTexture(gl: GLRenderingContext, faces: CubeFaces, mipm
if (loadedCount === 6) {
if (!destroyed) {
if (mipmaps) {
gl.generateMipmap(target);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.generateMipmap(target);
} else {
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter);
}
@@ -532,6 +553,7 @@ export function createCubeTexture(gl: GLRenderingContext, faces: CubeFaces, mipm
define: () => {},
load: () => {},
mipmap: () => {},
bind: (id: TextureId) => {
gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(target, texture);
@@ -577,6 +599,7 @@ export function createNullTexture(gl?: GLRenderingContext): Texture {
define: () => {},
load: () => {},
mipmap: () => {},
bind: (id: TextureId) => {
if (gl) {
gl.activeTexture(gl.TEXTURE0 + id);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -18,6 +18,9 @@ export type UniformKindValue = {
'v2': Vec2; 'v2[]': number[]
'v3': Vec3; 'v3[]': number[]
'v4': Vec4; 'v4[]': number[]
'iv2': Vec2; 'iv2[]': number[]
'iv3': Vec3; 'iv3[]': number[]
'iv4': Vec4; 'iv4[]': number[]
'm3': Mat3; 'm3[]': number[]
'm4': Mat4; 'm4[]': number[]
't': number; 't[]': number[]
@@ -36,6 +39,9 @@ export function getUniformType(gl: GLRenderingContext, kind: UniformKind) {
case 'v2': case 'v2[]': return gl.FLOAT_VEC2;
case 'v3': case 'v3[]': return gl.FLOAT_VEC3;
case 'v4': case 'v4[]': return gl.FLOAT_VEC4;
case 'iv2': case 'iv2[]': return gl.INT_VEC2;
case 'iv3': case 'iv3[]': return gl.INT_VEC3;
case 'iv4': case 'iv4[]': return gl.INT_VEC4;
case 'm3': case 'm3[]': return gl.FLOAT_MAT3;
case 'm4': case 'm4[]': return gl.FLOAT_MAT4;
default: console.error(`unknown uniform kind '${kind}'`);
@@ -56,6 +62,9 @@ function uniform1iv(gl: GLRenderingContext, location: number, value: any) { gl.u
function uniform2fv(gl: GLRenderingContext, location: number, value: any) { gl.uniform2fv(location, value); }
function uniform3fv(gl: GLRenderingContext, location: number, value: any) { gl.uniform3fv(location, value); }
function uniform4fv(gl: GLRenderingContext, location: number, value: any) { gl.uniform4fv(location, value); }
function uniform2iv(gl: GLRenderingContext, location: number, value: any) { gl.uniform2iv(location, value); }
function uniform3iv(gl: GLRenderingContext, location: number, value: any) { gl.uniform3iv(location, value); }
function uniform4iv(gl: GLRenderingContext, location: number, value: any) { gl.uniform4iv(location, value); }
function uniformMatrix3fv(gl: GLRenderingContext, location: number, value: any) { gl.uniformMatrix3fv(location, false, value); }
function uniformMatrix4fv(gl: GLRenderingContext, location: number, value: any) { gl.uniformMatrix4fv(location, false, value); }
@@ -68,6 +77,9 @@ function getUniformSetter(kind: UniformKind): UniformSetter {
case 'v2': case 'v2[]': return uniform2fv;
case 'v3': case 'v3[]': return uniform3fv;
case 'v4': case 'v4[]': return uniform4fv;
case 'iv2': case 'iv2[]': return uniform2iv;
case 'iv3': case 'iv3[]': return uniform3iv;
case 'iv4': case 'iv4[]': return uniform4iv;
case 'm3': case 'm3[]': return uniformMatrix3fv;
case 'm4': case 'm4[]': return uniformMatrix4fv;
}

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.365, IHM 1.18, MA 1.4.4.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.379, IHM 1.23, MA 1.4.5.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.365, IHM 1.18, MA 1.4.4.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.379, IHM 1.23, MA 1.4.5.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.1.0.
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.3.0.
*
* @author molstar/ciftools package
*/
@@ -18,7 +18,7 @@ const Matrix = Schema.Matrix;
export const CifCore_Schema = {
/**
* The CATEGORY of data items used to describe the parameters of
* the crystal unit cell and their measurement.
* the crystal unit cell.
*/
cell: {
/**
@@ -84,8 +84,8 @@ export const CifCore_Schema = {
* _chemical_formula.analytical, *.structural and *.sum. For the
* data item *.moiety the formula construction is broken up into
* residues or moieties, i.e. groups of atoms that form a molecular
* unit or molecular ion. The rules given below apply within each
* moiety but different requirements apply to the way that moieties
* unit or molecular ion. The rules given below apply within each
* moiety, but different requirements apply to the way that moieties
* are connected (see _chemical_formula.moiety).
*
* 1. Only recognized element symbols may be used.
@@ -109,7 +109,7 @@ export const CifCore_Schema = {
* depends on whether or not carbon is present. If carbon is
* present, the order should be: C, then H, then the other
* elements in alphabetical order of their symbol. If carbon is
* not present, the elements are listed purely in alphabetic order
* not present, the elements are listed purely in alphabetical order
* of their symbol. This is the 'Hill' system used by Chemical
* Abstracts. This ordering is used in _chemical_formula.moiety
* and _chemical_formula.sum.
@@ -165,7 +165,7 @@ export const CifCore_Schema = {
* stored in this dictionary.
*
* The commonly used Hermann-Mauguin symbol determines the
* space-group type uniquely but several different Hermann-Mauguin
* space-group type uniquely, but several different Hermann-Mauguin
* symbols may refer to the same space-group type. A
* Hermann-Mauguin symbol contains information on the choice of
* the basis, but not on the choice of origin.
@@ -205,13 +205,13 @@ export const CifCore_Schema = {
* Subscripts should appear without special symbols. Bars should
* be given as negative signs before the numbers to which they
* apply. The commonly used Hermann-Mauguin symbol determines the
* space-group type uniquely but a given space-group type may
* space-group type uniquely, but a given space-group type may
* be described by more than one Hermann-Mauguin symbol. The
* space-group type is best described using
* _space_group.IT_number or _space_group.name_Schoenflies. The
* full international Hermann-Mauguin symbol contains information
* about the choice of basis for monoclinic and orthorhombic
* space groups but does not give information about the choice
* space groups, but does not give information about the choice
* of origin. To define the setting uniquely use
* _space_group.name_Hall, or list the symmetry operations
* or generators.
@@ -282,8 +282,8 @@ export const CifCore_Schema = {
*/
publ_flag: str,
/**
* The set of data items which specify the symmetry operation codes
* which must be applied to the atom sites involved in the geometry angle.
* Data item specifying the symmetry operation codes applied to the atom
* sites involved in a specific geometric configuration.
*
* The symmetry code of each atom site as the symmetry-equivalent position
* number 'n' and the cell translation number 'pqr'. These numbers are
@@ -292,8 +292,9 @@ export const CifCore_Schema = {
* The character string n_pqr is composed as follows:
*
* n refers to the symmetry operation that is applied to the
* coordinates stored in _atom_site.fract_xyz. It must match a
* number given in _symmetry_equiv.pos_site_id.
* coordinates stored in _atom_site.fract_xyz. It must match
* a number given in _space_group_symop.id (or one of its
* aliases, such as _symmetry_equiv_pos_site_id).
*
* p, q and r refer to the translations that are subsequently
* applied to the symmetry transformed coordinates to generate
@@ -305,8 +306,8 @@ export const CifCore_Schema = {
*/
site_symmetry_1: str,
/**
* The set of data items which specify the symmetry operation codes
* which must be applied to the atom sites involved in the geometry angle.
* Data item specifying the symmetry operation codes applied to the atom
* sites involved in a specific geometric configuration.
*
* The symmetry code of each atom site as the symmetry-equivalent position
* number 'n' and the cell translation number 'pqr'. These numbers are
@@ -315,8 +316,9 @@ export const CifCore_Schema = {
* The character string n_pqr is composed as follows:
*
* n refers to the symmetry operation that is applied to the
* coordinates stored in _atom_site.fract_xyz. It must match a
* number given in _symmetry_equiv.pos_site_id.
* coordinates stored in _atom_site.fract_xyz. It must match
* a number given in _space_group_symop.id (or one of its
* aliases, such as _symmetry_equiv_pos_site_id).
*
* p, q and r refer to the translations that are subsequently
* applied to the symmetry transformed coordinates to generate
@@ -355,7 +357,7 @@ export const CifCore_Schema = {
* of the Internet concepts of Uniform Resource Name and
* Universal Resource Locator managed according to the
* specifications of the International DOI Foundation
* (see http://www.doi.org).
* (see https://www.doi.org/).
*/
block_doi: str,
},
@@ -414,23 +416,25 @@ export const CifCore_Schema = {
*/
calc_flag: str,
/**
* A code which identifies a cluster of atoms that show long range
* positional disorder but are locally ordered. Within each such
* cluster of atoms, _atom_site.disorder_group is used to identify
* the sites that are simultaneously occupied. This field is only
* needed if there is more than one cluster of disordered atoms
* showing independent local order.
* A code which identifies a cluster of atoms that show long range disorder
* but are locally ordered. Within each such cluster of atoms,
* _atom_site.disorder_group is used to identify the sites that are
* simultaneously occupied. This field is only needed if there is more than
* one cluster of disordered atoms showing independent local order.
*/
disorder_assembly: str,
/**
* A code that identifies a group of positionally disordered atom
* sites that are locally simultaneously occupied. Atoms that are
* positionally disordered over two or more sites (e.g. the H
* atoms of a methyl group that exists in two orientations) can
* be assigned to two or more groups. Sites belonging to the same
* group are simultaneously occupied, but those belonging to
* different groups are not. A minus prefix (e.g. "-1") is used to
* indicate sites disordered about a special position.
* A code that identifies a group of disordered atom sites that are locally
* simultaneously occupied. Atoms that are positionally disordered over two or
* more sites (e.g. the H atoms of a methyl group that exists in two
* orientations) should be assigned to two or more groups. Similarly, atoms
* that describe a specific alternative composition of a compositionally
* disordered site should be assigned to a distinct disorder group (e.g. a site
* that is partially occupied by Mg and Mn atoms should be described by
* assigning the Mg atom to one group and the Mn atom to another group). Sites
* belonging to the same group are simultaneously occupied, but those belonging
* to different groups are not. A minus prefix (e.g. "-1") is used to indicate
* sites disordered about a special position.
*/
disorder_group: str,
/**
@@ -491,6 +495,10 @@ export const CifCore_Schema = {
* Vol. A (2002). It is equal to the multiplicity of the general
* position divided by the order of the site symmetry given in
* _atom_site.site_symmetry_order.
*
* The _atom_site_symmetry_multiplicity form of this data name is
* deprecated because of historical inconsistencies in practice among
* structure refinement software packages and should not be used.
*/
site_symmetry_multiplicity: int,
/**
@@ -502,8 +510,8 @@ export const CifCore_Schema = {
type_symbol: str,
/**
* Isotropic atomic displacement parameter, or equivalent isotropic
* atomic displacement parameter, U(equiv), in angstroms squared,
* calculated from anisotropic atomic displacement parameters.
* atomic displacement parameter, U(equiv), in angstroms squared,
* calculated from anisotropic atomic displacement parameters.
*
* U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~.a~j~)]
*
@@ -514,8 +522,8 @@ export const CifCore_Schema = {
u_iso_or_equiv: float,
},
/**
* The CATEGORY of data items used to describe the anisotropic
* thermal parameters of the atomic sites in a crystal structure.
* The CATEGORY of data items used to describe the anisotropic atomic
* displacement parameters of the atomic sites in a crystal structure.
*/
atom_site_aniso: {
/**
@@ -526,94 +534,136 @@ export const CifCore_Schema = {
*/
label: str,
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u_11: float,
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u: Matrix(3, 3),
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u_12: float,
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u_13: float,
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u_22: float,
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u_23: float,
/**
* These are the standard anisotropic atomic displacement
* components in angstroms squared which appear in the
* structure factor term:
* These are the standard anisotropic atomic displacement components, in
* angstroms squared, which appear in the structure factor term:
*
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
*
* h = the Miller indices
* a* = the reciprocal-space cell lengths
*
* The unique elements of the real symmetric matrix are entered by row.
*
* The IUCr Commission on Nomenclature recommends the use of U for reporting
* atomic displacement parameters .
*
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
*
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
*/
u_33: float,
},
@@ -625,7 +675,7 @@ export const CifCore_Schema = {
/**
* A description of the atom(s) designated by this atom type. In
* most cases this will be the element name and oxidation state of
* a single atom species. For disordered or nonstoichiometric
* a single atom species. For disordered or nonstoichiometric
* structures it will describe a combination of atom species.
*/
description: str,

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.365, IHM 1.18, MA 1.4.4.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.379, IHM 1.23, MA 1.4.5.
*
* @author molstar/ciftools package
*/
@@ -3710,7 +3710,7 @@ export const mmCIF_Schema = {
/**
* The name of the database containing the dataset entry.
*/
db_name: Aliased<'PDB' | 'PDB-Dev' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'ProXL' | 'Other'>(str),
db_name: Aliased<'PDB' | 'PDB-Dev' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'ProXL' | 'jPOSTrepo' | 'iProX' | 'AlphaFoldDB' | 'Other'>(str),
/**
* The accession code for the database entry.
*/
@@ -4037,7 +4037,7 @@ export const mmCIF_Schema = {
/**
* The type of crosslinker used.
*/
linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'CYS' | 'SDA' | 'DSA' | 'BrdU' | 'LCSDA' | 'CDI' | 'ADH' | 'Other'>(str),
linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'CYS' | 'SDA' | 'DSA' | 'BrdU' | 'LCSDA' | 'CDI' | 'ADH' | 'L-Photo-Leucine' | 'KArGO' | 'BrEtY' | 'DSBU' | 'DSPP' | 'TBDSPP' | 'Other'>(str),
/**
* Identifier to the crosslinking dataset.
* This data item is a pointer to the _ihm_dataset_list.id in the

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
@@ -29,6 +29,7 @@ export class MolEncoder extends LigandEncoder {
// happens for the unknown ligands (UNL)
if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
let atomCount = 0;
let bondCount = 0;
let chiral = false;
@@ -55,6 +56,7 @@ export class MolEncoder extends LigandEncoder {
StringBuilder.writeSafe(ctab, ' 0');
StringBuilder.writeIntegerPadLeft(ctab, this.mapCharge(charge), 3);
StringBuilder.writeSafe(ctab, ' 0 0 0 0 0 0 0 0 0 0\n');
atomCount++;
if (stereo_config !== 'n') chiral = true;
// no data for metal ions
@@ -76,7 +78,7 @@ export class MolEncoder extends LigandEncoder {
});
// write counts line
StringBuilder.writeIntegerPadLeft(this.builder, atoms.size, 3);
StringBuilder.writeIntegerPadLeft(this.builder, atomCount, 3);
StringBuilder.writeIntegerPadLeft(this.builder, bondCount, 3);
StringBuilder.writeSafe(this.builder, ` 0 0 ${chiral ? 1 : 0} 0 0 0 0 0 0\n`);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
@@ -33,6 +33,7 @@ export class Mol2Encoder extends LigandEncoder {
const bondMap = this.componentBondData.entries.get(name)!;
// happens for the unknown ligands (UNL)
if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
let atomCount = 0;
let bondCount = 0;
const atoms = this.getAtoms(instance, source);
@@ -67,10 +68,11 @@ export class Mol2Encoder extends LigandEncoder {
const sybyl = bondMap?.map ? this.mapToSybyl(label_atom_id1, type_symbol1, bondMap) : type_symbol1;
StringBuilder.writeSafe(a, `${i1 + 1} ${label_atom_id1} ${atom1.Cartn_x.toFixed(3)} ${atom1.Cartn_y.toFixed(3)} ${atom1.Cartn_z.toFixed(3)} ${sybyl} 1 ${name} 0.000\n`);
atomCount++;
});
// could write something like 'SMALL\nNO_CHARGES', for now let's write **** indicating non-optional, yet missing, string values
StringBuilder.writeSafe(this.out, `@<TRIPOS>MOLECULE\n${name}\n${atoms.size} ${bondCount} 1\n****\n****\n\n`);
StringBuilder.writeSafe(this.out, `@<TRIPOS>MOLECULE\n${name}\n${atomCount} ${bondCount} 1\n****\n****\n\n`);
StringBuilder.writeSafe(this.out, StringBuilder.getString(a));
StringBuilder.writeSafe(this.out, StringBuilder.getString(b));
StringBuilder.writeSafe(this.out, `@<TRIPOS>SUBSTRUCTURE\n1 ${name} 1\n`);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 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>
@@ -49,7 +49,7 @@ export interface IntAdjacencyGraph<VertexIndex extends number, EdgeProps extends
export namespace IntAdjacencyGraph {
export type EdgePropsBase = { [name: string]: ArrayLike<any> }
export function areEqual<I extends number, P extends IntAdjacencyGraph.EdgePropsBase>(a: IntAdjacencyGraph<I, P>, b: IntAdjacencyGraph<I, P>) {
export function areEqual<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(a: IntAdjacencyGraph<VertexIndex, EdgeProps>, b: IntAdjacencyGraph<VertexIndex, EdgeProps>) {
if (a === b) return true;
if (a.vertexCount !== b.vertexCount || a.edgeCount !== b.edgeCount) return false;
@@ -149,12 +149,11 @@ export namespace IntAdjacencyGraph {
const a = this.xs[this.current], b = this.ys[this.current];
const oa = this.offsets[a] + this.bucketFill[a];
const ob = this.offsets[b] + this.bucketFill[b];
this.a[oa] = a;
this.b[oa] = b;
this.bucketFill[a]++;
const ob = this.offsets[b] + this.bucketFill[b];
this.a[ob] = b;
this.b[ob] = a;
this.bucketFill[b]++;
@@ -176,6 +175,13 @@ export namespace IntAdjacencyGraph {
prop[this.curB] = value;
}
assignDirectedProperty<T>(propA: { [i: number]: T }, valueA: T, propB: { [i: number]: T }, valueB: T) {
propA[this.curA] = valueA;
propA[this.curB] = valueB;
propB[this.curB] = valueA;
propB[this.curA] = valueB;
}
constructor(public vertexCount: number, public xs: ArrayLike<VertexIndex>, public ys: ArrayLike<VertexIndex>) {
this.edgeCount = xs.length;
this.offsets = new Int32Array(this.vertexCount + 1);
@@ -206,11 +212,11 @@ export namespace IntAdjacencyGraph {
edgeCount: number;
/** the size of the A and B arrays */
slotCount: number;
a: Int32Array;
b: Int32Array;
a: AssignableArrayLike<VertexIndex>;
b: AssignableArrayLike<VertexIndex>;
createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(edgeProps: EdgeProps) {
return create(this.offsets, this.a, this.b, this.edgeCount, edgeProps);
createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase, Props>(edgeProps: EdgeProps, props?: Props) {
return create<VertexIndex, EdgeProps, Props>(this.offsets, this.a, this.b, this.edgeCount, edgeProps, props);
}
/**
@@ -261,8 +267,8 @@ export namespace IntAdjacencyGraph {
}
this.offsets[this.vertexCount] = offset;
this.slotCount = offset;
this.a = new Int32Array(offset);
this.b = new Int32Array(offset);
this.a = new Int32Array(offset) as unknown as AssignableArrayLike<VertexIndex>;
this.b = new Int32Array(offset) as unknown as AssignableArrayLike<VertexIndex>;
}
}
@@ -295,13 +301,13 @@ export namespace IntAdjacencyGraph {
}
}
export function fromVertexPairs<V extends number>(vertexCount: number, xs: V[], ys: V[]) {
export function fromVertexPairs<VertexIndex extends number>(vertexCount: number, xs: VertexIndex[], ys: VertexIndex[]) {
const graphBuilder = new IntAdjacencyGraph.EdgeBuilder(vertexCount, xs, ys);
graphBuilder.addAllEdges();
return graphBuilder.createGraph({});
}
export function induceByVertices<V extends number, P extends IntAdjacencyGraph.EdgePropsBase>(graph: IntAdjacencyGraph<V, P>, vertexIndices: ArrayLike<number>): IntAdjacencyGraph<V, P> {
export function induceByVertices<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase, Props>(graph: IntAdjacencyGraph<VertexIndex, EdgeProps>, vertexIndices: ArrayLike<number>, props?: Props): IntAdjacencyGraph<VertexIndex, EdgeProps> {
const { b, offset, vertexCount, edgeProps } = graph;
const vertexMap = new Int32Array(vertexCount);
for (let i = 0, _i = vertexIndices.length; i < _i; i++) vertexMap[vertexIndices[i]] = i + 1;
@@ -316,8 +322,8 @@ export namespace IntAdjacencyGraph {
const newOffsets = new Int32Array(vertexIndices.length + 1);
const edgeIndices = new Int32Array(2 * newEdgeCount);
const newA = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<V>;
const newB = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<V>;
const newA = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<VertexIndex>;
const newB = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<VertexIndex>;
let eo = 0, vo = 0;
for (let i = 0; i < vertexCount; i++) {
if (vertexMap[i] === 0) continue;
@@ -326,20 +332,20 @@ export namespace IntAdjacencyGraph {
const bb = vertexMap[b[j]];
if (bb === 0) continue;
newA[eo] = aa as V;
newB[eo] = bb - 1 as V;
newA[eo] = aa as VertexIndex;
newB[eo] = bb - 1 as VertexIndex;
edgeIndices[eo] = j;
eo++;
}
newOffsets[++vo] = eo;
}
const newEdgeProps = {} as P;
for (const key of Object.keys(edgeProps) as (keyof P)[]) {
newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices) as P[keyof P];
const newEdgeProps = {} as EdgeProps;
for (const key of Object.keys(edgeProps) as (keyof EdgeProps)[]) {
newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices) as EdgeProps[keyof EdgeProps];
}
return create(newOffsets, newA, newB, newEdgeCount, newEdgeProps);
return create<VertexIndex, EdgeProps, Props>(newOffsets, newA, newB, newEdgeCount, newEdgeProps, props);
}
export function connectedComponents(graph: IntAdjacencyGraph<any, any>): { componentCount: number, componentIndex: Int32Array } {

View File

@@ -0,0 +1,167 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* This code has been modified from https://github.com/mrdoob/three.js/,
* copyright (c) 2010-2023 three.js authors. MIT License
*/
import { Mat4 } from './mat4';
import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
import { Quat } from './quat';
import { Vec3 } from './vec3';
import { clamp } from '../../interpolate';
interface Euler extends Array<number> { [d: number]: number, '@type': 'euler', length: 3 }
function Euler() {
return Euler.zero();
}
namespace Euler {
export type Order = 'XYZ' | 'YXZ' | 'ZXY' | 'ZYX' | 'YZX' | 'XZY'
export function zero(): Euler {
// force double backing array by 0.1.
const ret = [0.1, 0, 0];
ret[0] = 0.0;
return ret as any;
}
export function create(x: number, y: number, z: number): Euler {
const out = zero();
out[0] = x;
out[1] = y;
out[2] = z;
return out;
}
export function set(out: Euler, x: number, y: number, z: number) {
out[0] = x;
out[0] = y;
out[0] = z;
return out;
}
export function clone(a: Euler): Euler {
const out = zero();
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
return out;
}
export function copy(out: Euler, a: Euler) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
return out;
}
/**
* Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
*/
export function fromMat4(out: Euler, m: Mat4, order: Order): Euler {
const m11 = m[0], m12 = m[4], m13 = m[8];
const m21 = m[1], m22 = m[5], m23 = m[9];
const m31 = m[2], m32 = m[6], m33 = m[10];
switch (order) {
case 'XYZ':
out[1] = Math.asin(clamp(m13, -1, 1));
if (Math.abs(m13) < 0.9999999) {
out[0] = Math.atan2(-m23, m33);
out[2] = Math.atan2(-m12, m11);
} else {
out[0] = Math.atan2(m32, m22);
out[2] = 0;
}
break;
case 'YXZ':
out[0] = Math.asin(-clamp(m23, -1, 1));
if (Math.abs(m23) < 0.9999999) {
out[1] = Math.atan2(m13, m33);
out[2] = Math.atan2(m21, m22);
} else {
out[1] = Math.atan2(-m31, m11);
out[2] = 0;
}
break;
case 'ZXY':
out[0] = Math.asin(clamp(m32, -1, 1));
if (Math.abs(m32) < 0.9999999) {
out[1] = Math.atan2(-m31, m33);
out[2] = Math.atan2(-m12, m22);
} else {
out[1] = 0;
out[2] = Math.atan2(m21, m11);
}
break;
case 'ZYX':
out[1] = Math.asin(-clamp(m31, -1, 1));
if (Math.abs(m31) < 0.9999999) {
out[0] = Math.atan2(m32, m33);
out[2] = Math.atan2(m21, m11);
} else {
out[0] = 0;
out[2] = Math.atan2(-m12, m22);
}
break;
case 'YZX':
out[2] = Math.asin(clamp(m21, -1, 1));
if (Math.abs(m21) < 0.9999999) {
out[0] = Math.atan2(-m23, m22);
out[1] = Math.atan2(-m31, m11);
} else {
out[0] = 0;
out[1] = Math.atan2(m13, m33);
}
break;
case 'XZY':
out[2] = Math.asin(-clamp(m12, -1, 1));
if (Math.abs(m12) < 0.9999999) {
out[0] = Math.atan2(m32, m22);
out[1] = Math.atan2(m13, m11);
} else {
out[0] = Math.atan2(-m23, m33);
out[1] = 0;
}
break;
default:
assertUnreachable(order);
}
return out;
}
const _mat4 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat4;
export function fromQuat(out: Euler, q: Quat, order: Order) {
Mat4.fromQuat(_mat4, q);
return fromMat4(out, _mat4, order);
}
export function fromVec3(out: Euler, v: Vec3) {
return set(out, v[0], v[1], v[2]);
}
export function exactEquals(a: Euler, b: Euler) {
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
}
export function fromArray(e: Euler, array: ArrayLike<number>, offset: number) {
e[0] = array[offset + 0];
e[1] = array[offset + 1];
e[2] = array[offset + 2];
return e;
}
export function toArray<T extends NumberArray>(e: Euler, out: T, offset: number) {
out[offset + 0] = e[0];
out[offset + 1] = e[1];
out[offset + 2] = e[2];
return out;
}
}
export { Euler };

View File

@@ -407,12 +407,12 @@ namespace Mat3 {
return out;
}
const tmpR0 = [0.1, 0.0, 0.0] as Vec3;
const tmpR1 = [0.1, 0.0, 0.0] as Vec3;
const tmpR2 = [0.1, 0.0, 0.0] as Vec3;
const tmpR0xR1 = [0.1, 0.0, 0.0] as Vec3;
const tmpR0xR2 = [0.1, 0.0, 0.0] as Vec3;
const tmpR1xR2 = [0.1, 0.0, 0.0] as Vec3;
const tmpR0 = [0.1, 0.0, 0.0] as unknown as Vec3;
const tmpR1 = [0.1, 0.0, 0.0] as unknown as Vec3;
const tmpR2 = [0.1, 0.0, 0.0] as unknown as Vec3;
const tmpR0xR1 = [0.1, 0.0, 0.0] as unknown as Vec3;
const tmpR0xR2 = [0.1, 0.0, 0.0] as unknown as Vec3;
const tmpR1xR2 = [0.1, 0.0, 0.0] as unknown as Vec3;
/**
* Calculates the eigenvector for the given eigenvalue `e` of matrix `a`
*/

View File

@@ -23,6 +23,7 @@ import { Quat } from './quat';
import { degToRad } from '../../misc';
import { NumberArray } from '../../../mol-util/type-helpers';
import { Mat3 } from './mat3';
import { Euler } from './euler';
interface Mat4 extends Array<number> { [d: number]: number, '@type': 'mat4', length: 16 }
interface ReadonlyMat4 extends Array<number> { readonly [d: number]: number, '@type': 'mat4', length: 16 }
@@ -717,6 +718,82 @@ namespace Mat4 {
return out;
}
export function compose(out: Mat4, position: Vec3, quaternion: Quat, scale: Vec3) {
const [x, y, z, w] = quaternion;
const x2 = x + x, y2 = y + y, z2 = z + z;
const xx = x * x2, xy = x * y2, xz = x * z2;
const yy = y * y2, yz = y * z2, zz = z * z2;
const wx = w * x2, wy = w * y2, wz = w * z2;
const [sx, sy, sz] = scale;
out[0] = (1 - (yy + zz)) * sx;
out[1] = (xy + wz) * sx;
out[2] = (xz - wy) * sx;
out[3] = 0;
out[4] = (xy - wz) * sy;
out[5] = (1 - (xx + zz)) * sy;
out[6] = (yz + wx) * sy;
out[7] = 0;
out[8] = (xz + wy) * sz;
out[9] = (yz - wx) * sz;
out[10] = (1 - (xx + yy)) * sz;
out[11] = 0;
out[12] = position[0];
out[13] = position[1];
out[14] = position[2];
out[15] = 1;
return out;
}
const _v3 = [0, 0, 0] as unknown as Vec3;
const _m4 = zero();
export function decompose(m: Mat4, position: Vec3, quaternion: Quat, scale: Vec3) {
let sx = Vec3.magnitude(Vec3.set(_v3, m[0], m[1], m[2]));
const sy = Vec3.magnitude(Vec3.set(_v3, m[4], m[5], m[6]));
const sz = Vec3.magnitude(Vec3.set(_v3, m[8], m[9], m[10]));
// if determine is negative, we need to invert one scale
const det = determinant(m);
if (det < 0) sx = -sx;
position[0] = m[12];
position[1] = m[13];
position[2] = m[14];
// scale the rotation part
copy(_m4, m);
const invSX = 1 / sx;
const invSY = 1 / sy;
const invSZ = 1 / sz;
_m4[0] *= invSX;
_m4[1] *= invSX;
_m4[2] *= invSX;
_m4[4] *= invSY;
_m4[5] *= invSY;
_m4[6] *= invSY;
_m4[8] *= invSZ;
_m4[9] *= invSZ;
_m4[10] *= invSZ;
getRotation(quaternion, _m4);
scale[0] = sx;
scale[1] = sy;
scale[2] = sz;
return m;
}
export function makeTable(m: Mat4) {
let ret = '';
for (let i = 0; i < 4; i++) {
@@ -851,6 +928,94 @@ namespace Mat4 {
return out;
}
export function fromEuler(out: Mat4, euler: Euler, order: Euler.Order) {
const x = euler[0], y = euler[1], z = euler[2];
const a = Math.cos(x), b = Math.sin(x);
const c = Math.cos(y), d = Math.sin(y);
const e = Math.cos(z), f = Math.sin(z);
if (order === 'XYZ') {
const ae = a * e, af = a * f, be = b * e, bf = b * f;
out[0] = c * e;
out[4] = - c * f;
out[8] = d;
out[1] = af + be * d;
out[5] = ae - bf * d;
out[9] = - b * c;
out[2] = bf - ae * d;
out[6] = be + af * d;
out[10] = a * c;
} else if (order === 'YXZ') {
const ce = c * e, cf = c * f, de = d * e, df = d * f;
out[0] = ce + df * b;
out[4] = de * b - cf;
out[8] = a * d;
out[1] = a * f;
out[5] = a * e;
out[9] = - b;
out[2] = cf * b - de;
out[6] = df + ce * b;
out[10] = a * c;
} else if (order === 'ZXY') {
const ce = c * e, cf = c * f, de = d * e, df = d * f;
out[0] = ce - df * b;
out[4] = - a * f;
out[8] = de + cf * b;
out[1] = cf + de * b;
out[5] = a * e;
out[9] = df - ce * b;
out[2] = - a * d;
out[6] = b;
out[10] = a * c;
} else if (order === 'ZYX') {
const ae = a * e, af = a * f, be = b * e, bf = b * f;
out[0] = c * e;
out[4] = be * d - af;
out[8] = ae * d + bf;
out[1] = c * f;
out[5] = bf * d + ae;
out[9] = af * d - be;
out[2] = - d;
out[6] = b * c;
out[10] = a * c;
} else if (order === 'YZX') {
const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
out[0] = c * e;
out[4] = bd - ac * f;
out[8] = bc * f + ad;
out[1] = f;
out[5] = a * e;
out[9] = - b * e;
out[2] = - d * e;
out[6] = ad * f + bc;
out[10] = ac - bd * f;
} else if (order === 'XZY') {
const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
out[0] = c * e;
out[4] = - f;
out[8] = d * e;
out[1] = ac * f + bd;
out[5] = a * e;
out[9] = ad * f - bc;
out[2] = bc * f - ad;
out[6] = b * e;
out[10] = bd * f + ac;
}
// bottom row
out[3] = 0;
out[7] = 0;
out[11] = 0;
// last column
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
}
/**
* Generates a perspective projection (frustum) matrix with the given bounds
*/
@@ -1068,9 +1233,9 @@ namespace Mat4 {
return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));
}
const xAxis = [1, 0, 0] as Vec3;
const yAxis = [0, 1, 0] as Vec3;
const zAxis = [0, 0, 1] as Vec3;
const xAxis = [1, 0, 0] as unknown as Vec3;
const yAxis = [0, 1, 0] as unknown as Vec3;
const zAxis = [0, 0, 1] as unknown as Vec3;
/** Rotation matrix for 90deg around x-axis */
export const rotX90: ReadonlyMat4 = fromRotation(zero(), degToRad(90), xAxis);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2023 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>
@@ -25,7 +25,8 @@
import { Mat3 } from './mat3';
import { Vec3 } from './vec3';
import { EPSILON } from './common';
import { NumberArray } from '../../../mol-util/type-helpers';
import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
import { Euler } from './euler';
interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
interface ReadonlyQuat extends Array<number> { readonly [d: number]: number, '@type': 'quat', length: 4 }
@@ -238,6 +239,10 @@ namespace Quat {
return out;
}
export function dot(a: Quat, b: Quat) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
}
/**
* Creates a quaternion from the given 3x3 rotation matrix.
*
@@ -277,7 +282,64 @@ namespace Quat {
return out;
}
const fromUnitVec3Temp = [0, 0, 0] as Vec3;
export function fromEuler(out: Quat, euler: Euler, order: Euler.Order) {
const [x, y, z] = euler;
// http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
const c1 = Math.cos(x / 2);
const c2 = Math.cos(y / 2);
const c3 = Math.cos(z / 2);
const s1 = Math.sin(x / 2);
const s2 = Math.sin(y / 2);
const s3 = Math.sin(z / 2);
switch (order) {
case 'XYZ':
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
break;
case 'YXZ':
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
out[2] = c1 * c2 * s3 - s1 * s2 * c3;
out[3] = c1 * c2 * c3 + s1 * s2 * s3;
break;
case 'ZXY':
out[0] = s1 * c2 * c3 - c1 * s2 * s3;
out[1] = c1 * s2 * c3 + s1 * c2 * s3;
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
break;
case 'ZYX':
out[0] = s1 * c2 * c3 - c1 * s2 * s3;
out[1] = c1 * s2 * c3 + s1 * c2 * s3;
out[2] = c1 * c2 * s3 - s1 * s2 * c3;
out[3] = c1 * c2 * c3 + s1 * s2 * s3;
break;
case 'YZX':
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
out[1] = c1 * s2 * c3 + s1 * c2 * s3;
out[2] = c1 * c2 * s3 - s1 * s2 * c3;
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
break;
case 'XZY':
out[0] = s1 * c2 * c3 - c1 * s2 * s3;
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
out[3] = c1 * c2 * c3 + s1 * s2 * s3;
break;
default:
assertUnreachable(order);
}
return out;
}
const fromUnitVec3Temp = [0, 0, 0] as unknown as Vec3;
/** Quaternion from two normalized unit vectors. */
export function fromUnitVec3(out: Quat, a: Vec3, b: Vec3) {
// assumes a and b are normalized
@@ -395,9 +457,9 @@ namespace Quat {
*
* Both vectors are assumed to be unit length.
*/
const rotTmpVec3 = [0, 0, 0] as Vec3;
const rotTmpVec3UnitX = [1, 0, 0] as Vec3;
const rotTmpVec3UnitY = [0, 1, 0] as Vec3;
const rotTmpVec3 = [0, 0, 0] as unknown as Vec3;
const rotTmpVec3UnitX = [1, 0, 0] as unknown as Vec3;
const rotTmpVec3UnitY = [0, 1, 0] as unknown as Vec3;
export function rotationTo(out: Quat, a: Vec3, b: Vec3) {
const dot = Vec3.dot(a, b);
if (dot < -0.999999) {
@@ -440,7 +502,7 @@ namespace Quat {
* axes. Each axis is a vec3 and is expected to be unit length and
* perpendicular to all other specified axes.
*/
const axesTmpMat = [0, 0, 0, 0, 0, 0, 0, 0, 0] as Mat3;
const axesTmpMat = [0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat3;
export function setAxes(out: Quat, view: Vec3, right: Vec3, up: Vec3) {
axesTmpMat[0] = right[0];
axesTmpMat[3] = right[1];

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4 } from '../3d/mat4';
import { Euler } from '../3d/euler';
import { Quat } from '../3d/quat';
const t = [
[Euler.create(0, 0, 0), 'XYZ'],
[Euler.create(1, 0, 0), 'XYZ'],
[Euler.create(0, 1, 0), 'ZYX'],
] as const;
describe('Euler', () => {
it('fromMat4', () => {
for (const [e, o] of t) {
const m = Mat4.fromEuler(Mat4(), e, o);
const e2 = Euler.fromMat4(Euler(), m, o);
const m2 = Mat4.fromEuler(Mat4(), e2, o);
expect(Mat4.areEqual(m, m2, 0.0001)).toBe(true);
}
});
it('fromQuat', () => {
for (const [e, o] of t) {
const q = Quat.fromEuler(Quat(), e, o);
const e2 = Euler.fromQuat(Euler(), q, o);
const q2 = Quat.fromEuler(Quat(), e2, o);
expect(Quat.equals(q, q2)).toBe(true);
}
});
});

View File

@@ -7,10 +7,10 @@
import { Vec3 } from '../3d/vec3';
describe('vec3', () => {
const vec1 = [1, 2, 3] as Vec3;
const vec2 = [2, 3, 1] as Vec3;
const orthVec1 = [0, 1, 0] as Vec3;
const orthVec2 = [1, 0, 0] as Vec3;
const vec1 = Vec3.create(1, 2, 3);
const vec2 = Vec3.create(2, 3, 1);
const orthVec1 = Vec3.create(0, 1, 0);
const orthVec2 = Vec3.create(1, 0, 0);
it('angle calculation', () => {
expect(Vec3.angle(vec1, vec1) * 360 / (2 * Math.PI)).toBe(0.0);

View File

@@ -1,25 +1,30 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { Model } from '../../mol-model/structure/model/model';
import { Task } from '../../mol-task';
import { RuntimeContext, Task } from '../../mol-task';
import { ModelFormat } from '../format';
import { CifFrame, CIF } from '../../mol-io/reader/cif';
import { CifFrame, CIF, CifFile } from '../../mol-io/reader/cif';
import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
import { createModels } from './basic/parser';
import { ModelSymmetry } from './property/symmetry';
import { ModelSecondaryStructure } from './property/secondary-structure';
import { Table } from '../../mol-data/db';
import { Column, Table } from '../../mol-data/db';
import { AtomSiteAnisotrop } from './property/anisotropic';
import { ComponentBond } from './property/bonds/chem_comp';
import { StructConn } from './property/bonds/struct_conn';
import { Trajectory } from '../../mol-model/structure';
import { ArrayTrajectory, Trajectory } from '../../mol-model/structure';
import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
import { createBasic } from './basic/schema';
import { BasicSchema, createBasic } from './basic/schema';
import { CCD_Database } from '../../mol-io/reader/cif/schema/ccd';
import { EntityBuilder } from './common/entity';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { ComponentBuilder } from './common/component';
function modelSymmetryFromMmcif(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return;
@@ -83,6 +88,7 @@ namespace MmcifFormat {
export type Data = {
db: mmCIF_Database,
frame: CifFrame,
file?: CifFile,
/**
* Original source format. Some formats, including PDB, are converted
* to mmCIF before further processing.
@@ -93,14 +99,146 @@ namespace MmcifFormat {
return x?.kind === 'mmCIF';
}
export function fromFrame(frame: CifFrame, db?: mmCIF_Database, source?: ModelFormat): MmcifFormat {
export function fromFrame(frame: CifFrame, db?: mmCIF_Database, source?: ModelFormat, file?: CifFile): MmcifFormat {
if (!db) db = CIF.schema.mmCIF(frame);
return { kind: 'mmCIF', name: db._name, data: { db, frame, source } };
return { kind: 'mmCIF', name: db._name, data: { db, file, frame, source } };
}
}
export function trajectoryFromMmCIF(frame: CifFrame): Task<Trajectory> {
const format = MmcifFormat.fromFrame(frame);
export function trajectoryFromMmCIF(frame: CifFrame, file?: CifFile): Task<Trajectory> {
const format = MmcifFormat.fromFrame(frame, undefined, undefined, file);
const basic = createBasic(format.data.db, true);
return Task.create('Create mmCIF Model', ctx => createModels(basic, format, ctx));
}
export { CCDFormat };
type CCDFormat = ModelFormat<CCDFormat.Data>
namespace CCDFormat {
export type Data = {
db: CCD_Database,
frame: CifFrame
}
const CoordinateTypeProp = '__CcdCoordinateType__';
export type CoordinateType = 'ideal' | 'model'
export const CoordinateType = {
get(model: Model): CoordinateType | undefined {
return model._staticPropertyData[CoordinateTypeProp];
},
set(model: Model, type: CoordinateType) {
return model._staticPropertyData[CoordinateTypeProp] = type;
}
};
export function is(x?: ModelFormat): x is CCDFormat {
return x?.kind === 'CCD';
}
export function fromFrame(frame: CifFrame, db?: CCD_Database): CCDFormat {
if (!db) db = CIF.schema.CCD(frame);
return { kind: 'CCD', name: db._name, data: { db, frame } };
}
}
export function trajectoryFromCCD(frame: CifFrame): Task<Trajectory> {
const format = CCDFormat.fromFrame(frame);
return Task.create('Create CCD Models', ctx => createCcdModels(format.data.db, CCDFormat.fromFrame(frame), ctx));
}
async function createCcdModels(data: CCD_Database, format: CCDFormat, ctx: RuntimeContext) {
const ideal = await createCcdModel(data, format, { coordinateType: 'ideal', cartn_x: 'pdbx_model_Cartn_x_ideal', cartn_y: 'pdbx_model_Cartn_y_ideal', cartn_z: 'pdbx_model_Cartn_z_ideal' }, ctx);
const model = await createCcdModel(data, format, { coordinateType: 'model', cartn_x: 'model_Cartn_x', cartn_y: 'model_Cartn_y', cartn_z: 'model_Cartn_z' }, ctx);
const models = [];
if (ideal) models.push(ideal);
if (model) models.push(model);
for (let i = 0, il = models.length; i < il; ++i) {
Model.TrajectoryInfo.set(models[i], { index: i, size: models.length });
}
return new ArrayTrajectory(models);
}
type CCDProps = { coordinateType: CCDFormat.CoordinateType, cartn_x: 'model_Cartn_x' | 'pdbx_model_Cartn_x_ideal', cartn_y: 'model_Cartn_y' | 'pdbx_model_Cartn_y_ideal', cartn_z: 'model_Cartn_z' | 'pdbx_model_Cartn_z_ideal' };
async function createCcdModel(data: CCD_Database, format: CCDFormat, props: CCDProps, ctx: RuntimeContext) {
const { chem_comp, chem_comp_atom, chem_comp_bond } = data;
const { coordinateType, cartn_x, cartn_y, cartn_z } = props;
const name = chem_comp.name.value(0);
const id = chem_comp.id.value(0);
const { atom_id, charge, comp_id, pdbx_ordinal, type_symbol } = chem_comp_atom;
const atomCount = chem_comp_atom._rowCount;
const filteredRows: number[] = [];
for (let i = 0; i < atomCount; i++) {
if (chem_comp_atom[cartn_x].valueKind(i) > 0) continue;
filteredRows[filteredRows.length] = i;
}
const filteredRowCount = filteredRows.length;
const A = Column.ofConst('A', filteredRowCount, Column.Schema.str);
const seq_id = Column.ofConst(1, filteredRowCount, Column.Schema.int);
const entity_id = Column.ofConst('1', filteredRowCount, Column.Schema.str);
const occupancy = Column.ofConst(1, filteredRowCount, Column.Schema.float);
const model_num = Column.ofConst(1, filteredRowCount, Column.Schema.int);
const filteredAtomId = Column.view(atom_id, filteredRows);
const filteredCompId = Column.view(comp_id, filteredRows);
const filteredX = Column.view(chem_comp_atom[cartn_x], filteredRows);
const filteredY = Column.view(chem_comp_atom[cartn_y], filteredRows);
const filteredZ = Column.view(chem_comp_atom[cartn_z], filteredRows);
const filteredId = Column.view(pdbx_ordinal, filteredRows);
const filteredTypeSymbol = Column.view(type_symbol, filteredRows);
const filteredCharge = Column.view(charge, filteredRows);
const model_atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
auth_asym_id: A,
auth_atom_id: filteredAtomId,
auth_comp_id: filteredCompId,
auth_seq_id: seq_id,
Cartn_x: filteredX,
Cartn_y: filteredY,
Cartn_z: filteredZ,
id: filteredId,
label_asym_id: A,
label_atom_id: filteredAtomId,
label_comp_id: filteredCompId,
label_seq_id: seq_id,
label_entity_id: entity_id,
occupancy,
type_symbol: filteredTypeSymbol,
pdbx_PDB_model_num: model_num,
pdbx_formal_charge: filteredCharge
}, filteredRowCount);
const entityBuilder = new EntityBuilder();
entityBuilder.setNames([[id, `${name} (${coordinateType})`]]);
entityBuilder.getEntityId(id, MoleculeType.Unknown, 'A');
const componentBuilder = new ComponentBuilder(seq_id, type_symbol);
componentBuilder.setNames([[id, `${name} (${coordinateType})`]]);
componentBuilder.add(id, 0);
const basicModel = createBasic({
entity: entityBuilder.getEntityTable(),
chem_comp: componentBuilder.getChemCompTable(),
atom_site: model_atom_site
});
const models = await createModels(basicModel, format, ctx);
// all ideal or model coordinates might be absent
if (!models.representative) return;
const first = models.representative;
const entries = ComponentBond.getEntriesFromChemCompBond(chem_comp_bond);
ComponentBond.Provider.set(first, { data: chem_comp_bond, entries });
CCDFormat.CoordinateType.set(first, coordinateType);
return models.representative;
}

View File

@@ -49,7 +49,7 @@ function createAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assemb
return Assembly.create(id, details, operatorGroupsProvider(generators, matrices));
}
function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups {
export function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups {
return () => {
const groups: OperatorGroup[] = [];
@@ -71,7 +71,7 @@ function operatorGroupsProvider(generators: Generator[], matrices: Matrices): ()
};
}
function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
export function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
const { id, matrix, vector, _schema } = pdbx_struct_oper_list;
const matrices = new Map<string, Mat4>();
const t = Vec3();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 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>
@@ -34,8 +34,10 @@ function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementInde
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
builder.addNextEdge();
builder.assignProperty(key, props.key ? props.key[i] : -1);
builder.assignProperty(operatorA, props.operatorA ? props.operatorA[i] : -1);
builder.assignProperty(operatorB, props.operatorB ? props.operatorB[i] : -1);
builder.assignDirectedProperty(
operatorA, props.operatorA ? props.operatorA[i] : -1,
operatorB, props.operatorB ? props.operatorB[i] : -1
);
builder.assignProperty(order, props.order ? props.order[i] : 1);
builder.assignProperty(distance, props.distance ? props.distance[i] : -1);
builder.assignProperty(flag, props.flag ? props.flag[i] : BondType.Flag.Covalent);

View File

@@ -46,8 +46,8 @@ export function guessElementSymbolTokens(tokens: Tokens, str: string, start: num
TokenBuilder.add(tokens, s, s); // no reasonable guess, add empty token
}
const TwoCharElementNames = new Set(['NA', 'CL', 'FE', 'SI', 'BR', 'AS']);
const OneCharElementNames = new Set(['C', 'H', 'N', 'O', 'P', 'S']);
const TwoCharElementNames = new Set(['NA', 'CL', 'FE', 'SI', 'BR', 'AS', 'LI']);
const OneCharElementNames = new Set(['C', 'H', 'N', 'O', 'P', 'S', 'F', 'B']);
const reTrimSpacesAndNumbers = /^[\s\d]+|[\s\d]+$/g;
export function guessElementSymbolString(atomId: string, compId: string) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-23 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -52,8 +52,7 @@ namespace SIFTSMapping {
const model = loc.unit.model;
const data = Provider.get(model).value;
if (!data) return '';
const eI = loc.unit.elements[loc.element];
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
const rI = model.atomicHierarchy.residueAtomSegments.index[loc.element];
return data.accession[rI];
}
@@ -61,9 +60,9 @@ namespace SIFTSMapping {
const model = loc.unit.model;
const data = Provider.get(model).value;
if (!data) return;
const eI = loc.unit.elements[loc.element];
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
const rI = model.atomicHierarchy.residueAtomSegments.index[loc.element];
const dbName = data.dbName[rI];
if (!dbName) return;
return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`;
}

View File

@@ -286,7 +286,12 @@ export const AminoAcidNamesD = new Set([
export const AminoAcidNames = SetUtils.unionMany(AminoAcidNamesL, AminoAcidNamesD);
export const CommonProteinCaps = new Set([
'NME', 'ACE'
'NME', 'ACE', 'NH2', 'FOR', 'FMT'
// not including the following
// 'E1H' GFP backbone fragmentation in 2G16
// 'HOA' complexes zinc
// 'NEH' ubiquitine linker
// 'MOH' part of peptidomimetics
]);
export const RnaBaseNames = new Set([

View File

@@ -6,4 +6,4 @@
* @author molstar/chem-comp-dict/create-ions cli
*/
export const IonNames = new Set(['118', '119', '543', '1AL', '1CU', '2FK', '2HP', '2OF', '3CO', '3MT', '3NI', '3OF', '3P8', '4MO', '4PU', '4TI', '6MO', 'ACT', 'AG', 'AL', 'ALF', 'AM', 'ATH', 'AU', 'AU3', 'AUC', 'AZI', 'BA', 'BCT', 'BEF', 'BF4', 'BO4', 'BR', 'BS3', 'BSY', 'CA', 'CAC', 'CD', 'CD1', 'CD3', 'CD5', 'CE', 'CF', 'CHT', 'CL', 'CO', 'CO3', 'CO5', 'CON', 'CR', 'CS', 'CSB', 'CU', 'CU1', 'CU3', 'CUA', 'CUZ', 'CYN', 'DME', 'DMI', 'DSC', 'DTI', 'DY', 'E4N', 'EDR', 'EMC', 'ER3', 'EU', 'EU3', 'F', 'FE', 'FE2', 'FPO', 'GA', 'GD3', 'GEP', 'HAI', 'HG', 'HGC', 'IN', 'IOD', 'IR', 'IR3', 'IRI', 'IUM', 'K', 'KO4', 'LA', 'LCO', 'LCP', 'LI', 'LU', 'MAC', 'MG', 'MH2', 'MH3', 'MLI', 'MMC', 'MN', 'MN3', 'MN5', 'MN6', 'MO1', 'MO2', 'MO3', 'MO4', 'MO5', 'MO6', 'MOO', 'MOS', 'MOW', 'MW1', 'MW2', 'MW3', 'NA', 'NA2', 'NA5', 'NA6', 'NAO', 'NAW', 'ND', 'NET', 'NH4', 'NI', 'NI1', 'NI2', 'NI3', 'NO2', 'NO3', 'NRU', 'O4M', 'OAA', 'OC1', 'OC2', 'OC3', 'OC4', 'OC5', 'OC6', 'OC7', 'OC8', 'OCL', 'OCM', 'OCN', 'OCO', 'OF1', 'OF2', 'OF3', 'OH', 'OS', 'OS4', 'OXL', 'PB', 'PBM', 'PD', 'PDV', 'PER', 'PI', 'PO3', 'PO4', 'PR', 'PT', 'PT4', 'PTN', 'RB', 'RH3', 'RHD', 'RU', 'SB', 'SCN', 'SE4', 'SEK', 'SM', 'SMO', 'SO3', 'SO4', 'SR', 'T1A', 'TB', 'TBA', 'TCN', 'TEA', 'TH', 'THE', 'TL', 'TMA', 'TRA', 'UNX', 'V', 'VN3', 'VO4', 'W', 'WO5', 'Y1', 'YB', 'YB2', 'YH', 'YT3', 'ZCM', 'ZN', 'ZN2', 'ZN3', 'ZNO', 'ZO3', 'ZR', 'NCO', 'OHX']);
export const IonNames = new Set(['118', '119', '543', '1AL', '1CU', '2FK', '2HP', '2OF', '3CO', '3MT', '3NI', '3OF', '3P8', '4MO', '4PU', '4TI', '6MO', 'ACT', 'AG', 'AL', 'ALF', 'AM', 'ATH', 'AU', 'AU3', 'AUC', 'AZI', 'BA', 'BCT', 'BEF', 'BF4', 'BO4', 'BR', 'BS3', 'BSY', 'CA', 'CAC', 'CD', 'CD1', 'CD3', 'CD5', 'CE', 'CF', 'CHT', 'CL', 'CO', 'CO3', 'CO5', 'CON', 'CR', 'CS', 'CSB', 'CU', 'CU1', 'CU3', 'CUA', 'CUZ', 'CYN', 'DME', 'DMI', 'DSC', 'DTI', 'DY', 'E4N', 'EDR', 'EMC', 'ER3', 'EU', 'EU3', 'F', 'FE', 'FE2', 'FPO', 'GA', 'GD3', 'GEP', 'HAI', 'HG', 'HGC', 'IN', 'IOD', 'IR', 'IR3', 'IRI', 'IUM', 'K', 'KO4', 'LA', 'LCO', 'LCP', 'LI', 'LU', 'MAC', 'MG', 'MH2', 'MH3', 'MLI', 'MMC', 'MN', 'MN3', 'MN5', 'MN6', 'MO1', 'MO2', 'MO3', 'MO4', 'MO5', 'MO6', 'MOO', 'MOS', 'MOW', 'MW1', 'MW2', 'MW3', 'NA', 'NA2', 'NA5', 'NA6', 'NAO', 'NAW', 'ND', 'NET', 'NH4', 'NI', 'NI1', 'NI2', 'NI3', 'NO2', 'NO3', 'NRU', 'NT3', 'O4M', 'OAA', 'OC1', 'OC2', 'OC3', 'OC4', 'OC5', 'OC6', 'OC7', 'OC8', 'OCL', 'OCM', 'OCN', 'OCO', 'OF1', 'OF2', 'OF3', 'OH', 'OS', 'OS4', 'OXL', 'PB', 'PBM', 'PD', 'PDV', 'PER', 'PI', 'PO3', 'PO4', 'PR', 'PT', 'PT4', 'PTN', 'RB', 'RH3', 'RHD', 'RHF', 'RU', 'SB', 'SCN', 'SE4', 'SEK', 'SM', 'SMO', 'SO3', 'SO4', 'SR', 'T1A', 'TB', 'TBA', 'TCN', 'TEA', 'TH', 'THE', 'TL', 'TMA', 'TRA', 'UNX', 'V', 'VN3', 'VO4', 'W', 'WO5', 'Y1', 'YB', 'YB2', 'YH', 'YT3', 'ZCM', 'ZN', 'ZN2', 'ZN3', 'ZNO', 'ZO3', 'ZR', 'NCO', 'OHX']);

File diff suppressed because one or more lines are too long

View File

@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
import { computeCarbohydrates } from './carbohydrates/compute';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { idFactory } from '../../../mol-util/id-factory';
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
import { GridLookup3D } from '../../../mol-math/geometry';
import { UUID } from '../../../mol-util';
import { CustomProperties } from '../../custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
@@ -1213,25 +1213,20 @@ namespace Structure {
const lookup = structure.lookup3d;
const imageCenter = Vec3();
const bbox = Box3D();
const rvec = Vec3.create(maxRadius, maxRadius, maxRadius);
for (const unit of structure.units) {
if (!validUnit(unit)) continue;
for (const unitA of structure.units) {
if (!validUnit(unitA)) continue;
const bs = unit.boundary.sphere;
Box3D.expand(bbox, unit.boundary.box, rvec);
Vec3.transformMat4(imageCenter, bs.center, unit.conformation.operator.matrix);
const bs = unitA.boundary.sphere;
Vec3.transformMat4(imageCenter, bs.center, unitA.conformation.operator.matrix);
const closeUnits = lookup.findUnitIndices(imageCenter[0], imageCenter[1], imageCenter[2], bs.radius + maxRadius);
for (let i = 0; i < closeUnits.count; i++) {
const other = structure.units[closeUnits.indices[i]];
if (unit.id >= other.id) continue;
const unitB = structure.units[closeUnits.indices[i]];
if (unitA.id >= unitB.id) continue;
if (!validUnit(unitB) || !validUnitPair(unitA, unitB)) continue;
if (other.elements.length > 3 && !Box3D.overlaps(bbox, other.boundary.box)) continue;
if (!validUnit(other) || !validUnitPair(unit, other)) continue;
if (other.elements.length >= unit.elements.length) callback(unit, other);
else callback(other, unit);
if (unitB.elements.length >= unitA.elements.length) callback(unitA, unitB);
else callback(unitB, unitA);
}
}
}

View File

@@ -243,10 +243,8 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
...p,
validUnit: (props && props.validUnit) || (u => Unit.isAtomic(u)),
validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
// In case both units have a struct conn record, ignore other criteria
if (hasCommonStructConnRecord(a, b)) {
return Structure.validUnitPair(s, a, b);
}
const isValidPair = Structure.validUnitPair(s, a, b);
if (!isValidPair) return false;
const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;
@@ -258,7 +256,13 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
const notIonA = (!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Ion);
const notIonB = (!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Ion);
const notIon = notIonA && notIonB;
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
const check = (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
if (!check) {
// In case both units have a struct conn record, ignore other criteria
return hasCommonStructConnRecord(a, b);
}
return true;
}),
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 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>
@@ -94,6 +94,8 @@ export namespace StructureRepresentationPresetProvider {
}
export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
if (!plugin.state.hasBehavior(StructureFocusRepresentation)) return;
return plugin.state.updateBehavior(StructureFocusRepresentation, p => {
const c = createStructureColorThemeParams(plugin, structure, 'ball-and-stick', themeName || 'element-symbol', themeParams);
p.surroundingsParams.colorTheme = c;

View File

@@ -94,6 +94,8 @@ export class LociLabelManager {
constructor(public ctx: PluginContext) {
ctx.managers.interactivity.lociHighlights.addProvider((loci, action, noRender) => {
if (this.providers.length === 0) return;
this.mark(loci, action);
if (!noRender) this.showLabels();
});

View File

@@ -29,9 +29,11 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
static DefaultNextSnapshotDelayInMs = 1500;
private entryMap = new Map<string, PluginStateSnapshotManager.Entry>();
private defaultSnapshotId: UUID | undefined = undefined;
readonly events = {
changed: this.ev()
changed: this.ev(),
opened: this.ev(),
};
getIndex(e: PluginStateSnapshotManager.Entry) {
@@ -66,6 +68,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
const old = this.getEntry(id);
if (!old) return;
this.defaultSnapshotId = undefined;
if (old?.image) this.plugin.managers.asset.delete(old.image);
const idx = this.getIndex(old);
// The id changes here!
@@ -171,20 +175,27 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
}
private async syncCurrent(options?: { name?: string, description?: string, params?: PluginState.SnapshotParams }) {
const isEmpty = this.state.entries.size === 0;
const canReplace = this.state.entries.size === 1 && this.state.current && this.state.current === this.defaultSnapshotId;
if (!isEmpty && !canReplace) return;
const snapshot = this.plugin.state.getSnapshot(options?.params);
if (this.state.entries.size === 0 || !this.state.current) {
this.add(PluginStateSnapshotManager.Entry(snapshot, { name: options?.name, description: options?.description }));
} else {
const image = (options?.params?.image ?? this.plugin.state.snapshotParams.value.image) ? await PluginStateSnapshotManager.getCanvasImageAsset(this.plugin, `${snapshot.id}-image.png`) : undefined;
if (isEmpty) {
this.add(PluginStateSnapshotManager.Entry(snapshot, { name: options?.name, description: options?.description, image }));
} else if (canReplace) {
// Replace the current state only if there is a single snapshot that has been created automatically
const current = this.getEntry(this.state.current);
if (current?.image) this.plugin.managers.asset.delete(current.image);
const image = (options?.params?.image ?? this.plugin.state.snapshotParams.value.image) ? await PluginStateSnapshotManager.getCanvasImageAsset(this.plugin, `${snapshot.id}-image.png`) : undefined;
// TODO: this replaces the current snapshot which is not always intended
this.replace(this.state.current, snapshot, { image });
this.replace(this.state.current!, snapshot, { image });
}
this.defaultSnapshotId = snapshot.id;
}
async getStateSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean, params?: PluginState.SnapshotParams }): Promise<PluginStateSnapshotManager.StateSnapshot> {
// TODO: diffing and all that fancy stuff
await this.syncCurrent(options);
return {
@@ -242,11 +253,11 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
const snapshot = JSON.parse(data);
if (PluginStateSnapshotManager.isStateSnapshot(snapshot)) {
return this.setStateSnapshot(snapshot);
await this.setStateSnapshot(snapshot);
} else if (PluginStateSnapshotManager.isStateSnapshot(snapshot.data)) {
return this.setStateSnapshot(snapshot.data);
await this.setStateSnapshot(snapshot.data);
} else {
this.plugin.state.setSnapshot(snapshot);
await this.plugin.state.setSnapshot(snapshot);
}
} else {
const data = await this.plugin.runTask(readFromFile(file, 'zip'));
@@ -270,8 +281,9 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
}
const snapshot = JSON.parse(stateData);
return this.setStateSnapshot(snapshot);
await this.setStateSnapshot(snapshot);
}
this.events.opened.next(void 0);
} catch (e) {
console.error(e);
this.plugin.log.error('Error reading state');

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 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>
@@ -12,7 +12,7 @@ import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { QueryContext, Structure, StructureElement, StructureQuery, StructureSelection } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { StateObjectRef } from '../../../mol-state';
import { StateObjectRef, StateSelection } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { structureElementStatsLabel } from '../../../mol-theme/label';
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
@@ -35,6 +35,13 @@ const HISTORY_CAPACITY = 24;
export type StructureSelectionModifier = 'add' | 'remove' | 'intersect' | 'set'
export type StructureSelectionSnapshot = {
entries: {
ref: string
bundle: StructureElement.Bundle
}[]
}
export class StructureSelectionManager extends StatefulPluginComponent<StructureSelectionManagerState> {
readonly events = {
changed: this.ev<undefined>(),
@@ -484,6 +491,31 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
}
}
getSnapshot(): StructureSelectionSnapshot {
const entries: StructureSelectionSnapshot['entries'] = [];
this.entries.forEach((entry, ref) => {
entries.push({
ref,
bundle: StructureElement.Bundle.fromLoci(entry.selection)
});
});
return { entries };
}
setSnapshot(snapshot: StructureSelectionSnapshot) {
this.entries.clear();
for (const { ref, bundle } of snapshot.entries) {
const structure = this.plugin.state.data.select(StateSelection.Generators.byRef(ref))[0]?.obj?.data as Structure;
if (!structure) continue;
const loci = StructureElement.Bundle.toLoci(bundle, structure);
this.fromLoci('set', loci, false);
}
}
constructor(private plugin: PluginContext) {
super({ entries: new Map(), additionsHistory: [], stats: SelectionStats() });

View File

@@ -1,8 +1,9 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { parseDcd } from '../../mol-io/reader/dcd/parser';
@@ -12,7 +13,7 @@ import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { trajectoryFromCCD, trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
import { Coordinates, Model, Queries, QueryContext, Structure, StructureElement, StructureQuery, StructureSelection as Sel, Topology, ArrayTrajectory, Trajectory } from '../../mol-model/structure';
@@ -272,14 +273,18 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
params(a) {
if (!a) {
return {
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header parameter and parse all datablocks into a single trajectory.' })),
blockHeader: PD.Optional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.', hideIf: p => p.loadAllBlocks === true })),
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header and Block Index parameters and parse all datablocks into a single trajectory.' })),
blockHeader: PD.Optional(PD.Text(void 0, { description: 'Header of the block to parse. If not specifed, Block Index parameter applies.', hideIf: p => p.loadAllBlocks === true })),
blockIndex: PD.Optional(PD.Numeric(0, { min: 0, step: 1 }, { description: 'Zero-based index of the block to parse. Only applies when Block Header parameter is not specified.', hideIf: p => p.loadAllBlocks === true || p.blockHeader })),
};
}
const { blocks } = a.data;
const headers = blocks.map(b => [b.header, b.header] as [string, string]);
headers.push(['', '[Use Block Index]']);
return {
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header parameter and parse all data blocks into a single trajectory.' })),
blockHeader: PD.Optional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse', hideIf: p => p.loadAllBlocks === true })),
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header and Block Index parameters and parse all data blocks into a single trajectory.' })),
blockHeader: PD.Optional(PD.Select(blocks[0] && blocks[0].header, headers, { description: 'Header of the block to parse. If not specifed, Block Index parameter applies.', hideIf: p => p.loadAllBlocks === true })),
blockIndex: PD.Optional(PD.Numeric(0, { min: 0, step: 1, max: blocks.length - 1 }, { description: 'Zero-based index of the block to parse. Only applies when Block Header parameter is not specified.', hideIf: p => p.loadAllBlocks === true || p.blockHeader })),
};
}
})({
@@ -300,10 +305,11 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
}
trajectory = new ArrayTrajectory(models);
} else {
const header = params.blockHeader || a.data.blocks[0].header;
const header = params.blockHeader || a.data.blocks[params.blockIndex ?? 0].header;
const block = a.data.blocks.find(b => b.header === header);
if (!block) throw new Error(`Data block '${[header]}' not found.`);
trajectory = await trajectoryFromMmCIF(block).runInContext(ctx);
const isCcd = block.categoryNames.includes('chem_comp_atom') && !block.categoryNames.includes('atom_site') && !block.categoryNames.includes('ihm_sphere_obj_site') && !block.categoryNames.includes('ihm_gaussian_obj_site');
trajectory = isCcd ? await trajectoryFromCCD(block).runInContext(ctx) : await trajectoryFromMmCIF(block, a.data).runInContext(ctx);
}
if (trajectory.frameCount === 0) throw new Error('No models found.');
const props = trajectoryProps(trajectory);
@@ -1086,7 +1092,7 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({
from: SO.Format.Ply,
to: SO.Shape.Provider,
params(a) {
return { };
return {};
}
})({
apply({ a, params }) {

View File

@@ -172,6 +172,8 @@ const _VisibilityOutlined = <svg width='24px' height='24px' viewBox='0 0 24 24'>
export function VisibilityOutlinedSvg() { return _VisibilityOutlined; }
const _Warning = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z' /></svg>;
export function WarningSvg() { return _Warning; }
const _ContentCut = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M 9.64 7.64 c 0.23 -0.5 0.36 -1.05 0.36 -1.64 c 0 -2.21 -1.79 -4 -4 -4 S 2 3.79 2 6 s 1.79 4 4 4 c 0.59 0 1.14 -0.13 1.64 -0.36 L 10 12 l -2.36 2.36 C 7.14 14.13 6.59 14 6 14 c -2.21 0 -4 1.79 -4 4 s 1.79 4 4 4 s 4 -1.79 4 -4 c 0 -0.59 -0.13 -1.14 -0.36 -1.64 L 12 14 l 7 7 h 3 v -1 L 9.64 7.64 Z M 6 8 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 0 12 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 6 -7.5 c -0.28 0 -0.5 -0.22 -0.5 -0.5 s 0.22 -0.5 0.5 -0.5 s 0.5 0.22 0.5 0.5 s -0.22 0.5 -0.5 0.5 Z M 19 3 l -6 6 l 2 2 l 7 -7 V 3 Z' /></svg>;
export function ContentCutSvg() { return _ContentCut; }
// Aliases

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 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>
@@ -23,12 +23,46 @@ import { Asset } from '../mol-util/assets';
import { BehaviorSubject } from 'rxjs';
import { useBehavior } from './hooks/use-behavior';
export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> {
render() {
return <PluginReactContext.Provider value={this.props.plugin}>
export function Plugin({ plugin }: { plugin: PluginUIContext }) {
if (plugin.isInitialized) {
return <PluginReactContext.Provider value={plugin}>
<Layout />
</PluginReactContext.Provider>;
}
return <PluginInitWrapper plugin={plugin} />;
}
type LoadState =
| { kind: 'initialized' }
| { kind: 'pending' }
| { kind: 'error', message: string }
function PluginInitWrapper({ plugin }: { plugin: PluginUIContext }) {
const [state, setState] = React.useState<LoadState>({ kind: 'pending' });
React.useEffect(() => {
setState({ kind: 'pending' });
let mounted = true;
plugin.initialized.then(() => {
if (mounted) setState({ kind: 'initialized' });
}).catch(err => {
if (mounted) setState({ kind: 'error', message: `${err}` });
});
return () => { mounted = false; };
}, [plugin]);
if (state.kind === 'pending') return null;
if (state.kind === 'error') {
return <div className='msp-plugin'>
<div className='msp-plugin-init-error'>Initialization error: {state.message}</div>
</div>;
}
return <PluginReactContext.Provider value={plugin}>
<Layout />
</PluginReactContext.Provider>;
}
export class PluginContextContainer extends React.Component<{ plugin: PluginUIContext, children?: any }> {

View File

@@ -29,5 +29,10 @@
color: $font-color;
}
.msp-plugin-init-error {
white-space: pre;
margin: $control-spacing;
}
background: $default-background;
}

View File

@@ -21,6 +21,7 @@ class ToastEntry extends PluginUIComponent<{ entry: PluginToastManager.Entry }>
const entry = this.props.entry;
const message = typeof entry.message === 'string'
? <div dangerouslySetInnerHTML={{ __html: entry.message }} />
// @ts-ignore // TODO: handle type better
: <div><entry.message /></div>;
return <div className='msp-toast-entry'>

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