mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
253 Commits
v3.0.0-dev
...
v3.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eae7c11c55 | ||
|
|
b8251e1ade | ||
|
|
2ff2b9f348 | ||
|
|
164e3f3343 | ||
|
|
4901a1bd87 | ||
|
|
cd194cca65 | ||
|
|
1748efbc18 | ||
|
|
4d60b40403 | ||
|
|
6e5a41879f | ||
|
|
c5f9eb54da | ||
|
|
aebbfeb061 | ||
|
|
a0a5a6b578 | ||
|
|
6bdafb85d7 | ||
|
|
0dd7debf5d | ||
|
|
962b9ee7af | ||
|
|
15bcc5df88 | ||
|
|
8495d834c8 | ||
|
|
7282399709 | ||
|
|
780bdd6e7e | ||
|
|
ad08e7c67f | ||
|
|
ff089964ca | ||
|
|
990191529a | ||
|
|
ce1bec12b4 | ||
|
|
703ea9af53 | ||
|
|
df0227ae1e | ||
|
|
486d12b6ac | ||
|
|
9c18375ab4 | ||
|
|
e1708aed68 | ||
|
|
a42d778b84 | ||
|
|
7692b59c7c | ||
|
|
82de9b36b3 | ||
|
|
49b3c8f65f | ||
|
|
90ad32d936 | ||
|
|
2509e91f1a | ||
|
|
80dc2219e4 | ||
|
|
931cb0fa7d | ||
|
|
4383f2ea90 | ||
|
|
add79dc242 | ||
|
|
b6e142f04c | ||
|
|
105f6c3041 | ||
|
|
4babbb65c1 | ||
|
|
878159f7ed | ||
|
|
c320386019 | ||
|
|
1e7a0159f0 | ||
|
|
bbf4f1d1d3 | ||
|
|
8cd1c69c76 | ||
|
|
2372a878ac | ||
|
|
9bec644997 | ||
|
|
650d38dff8 | ||
|
|
097277e397 | ||
|
|
82a4d5eedf | ||
|
|
2a00248812 | ||
|
|
9841c773cb | ||
|
|
b21a78ad14 | ||
|
|
7329fa597d | ||
|
|
f1d8f0ecb4 | ||
|
|
1d127f2364 | ||
|
|
b244405cc3 | ||
|
|
38adfe0ca6 | ||
|
|
6f0d798847 | ||
|
|
6e58bfd2b0 | ||
|
|
bc2e8d8ac4 | ||
|
|
7be654d47f | ||
|
|
bfe46e3604 | ||
|
|
008b597fc5 | ||
|
|
c4b4f2e3b1 | ||
|
|
76ee97301b | ||
|
|
289dc09eae | ||
|
|
f23f84f0f3 | ||
|
|
62259f3295 | ||
|
|
854a430a12 | ||
|
|
d27cdb5637 | ||
|
|
7d12d9ee90 | ||
|
|
d70a4ff347 | ||
|
|
49541558d1 | ||
|
|
7b00a1227c | ||
|
|
7800603c81 | ||
|
|
fca00c8116 | ||
|
|
00fa549e44 | ||
|
|
36181b6b87 | ||
|
|
88a95162e9 | ||
|
|
9c1d59a2c8 | ||
|
|
fdd894956a | ||
|
|
eb6dc0859d | ||
|
|
b99026bba2 | ||
|
|
6fa50eb8d5 | ||
|
|
e5046f15a9 | ||
|
|
09f1c066a0 | ||
|
|
ed2f0b34c9 | ||
|
|
c7f75861de | ||
|
|
ccd04dbc9d | ||
|
|
e71f8d2c10 | ||
|
|
d9b4c60239 | ||
|
|
103c1fca21 | ||
|
|
49559bf5fb | ||
|
|
26dceabf83 | ||
|
|
abe506182e | ||
|
|
582a0e2a38 | ||
|
|
2784ccf379 | ||
|
|
0ad1d578fe | ||
|
|
31fd1c9c68 | ||
|
|
9c961297a2 | ||
|
|
b920053349 | ||
|
|
0a5c764e4a | ||
|
|
b9a71c83ff | ||
|
|
3255f207d0 | ||
|
|
e3b4ca8862 | ||
|
|
6810793015 | ||
|
|
1feb3c2095 | ||
|
|
f2da6033d0 | ||
|
|
28bc212132 | ||
|
|
1a7c62eec6 | ||
|
|
de67dbacba | ||
|
|
57223a0f9a | ||
|
|
2ad0754b90 | ||
|
|
3ecb3af57b | ||
|
|
ec4f15f549 | ||
|
|
2458ea7b92 | ||
|
|
c5e6bedf11 | ||
|
|
8528e5a666 | ||
|
|
6ed232b3d9 | ||
|
|
f8aae8cbd1 | ||
|
|
00c2517045 | ||
|
|
99b043a929 | ||
|
|
5900e27e39 | ||
|
|
1b79d34907 | ||
|
|
fc52e29c92 | ||
|
|
df23b3c0fe | ||
|
|
5e25716c98 | ||
|
|
f70a10bc56 | ||
|
|
0ccb045f4e | ||
|
|
fa18d0d852 | ||
|
|
687c4342fb | ||
|
|
9459af46b8 | ||
|
|
fc5832747a | ||
|
|
01205d244b | ||
|
|
31a555255a | ||
|
|
fbb60c9493 | ||
|
|
9f953ef51c | ||
|
|
4871f1547c | ||
|
|
d6413529f4 | ||
|
|
724cf5a0da | ||
|
|
b6847907ca | ||
|
|
fb54a1aed7 | ||
|
|
9815318daf | ||
|
|
bc13b98111 | ||
|
|
238b70c121 | ||
|
|
dcd23bc0cb | ||
|
|
4694ea85fa | ||
|
|
bdb17743d7 | ||
|
|
8b76ff2461 | ||
|
|
ea5421002b | ||
|
|
76ac55917d | ||
|
|
5cfb2376c4 | ||
|
|
6b9d3fd80e | ||
|
|
a09752b62e | ||
|
|
3134e1d9f9 | ||
|
|
e94ecf2a0b | ||
|
|
1bd4d841a1 | ||
|
|
8e349f47a5 | ||
|
|
119c0a4231 | ||
|
|
f009f533e0 | ||
|
|
3ab0c1e509 | ||
|
|
e3d264e239 | ||
|
|
9bd60f8e8e | ||
|
|
bcec1d9637 | ||
|
|
1b431b1d20 | ||
|
|
23c2dcdfd4 | ||
|
|
dd415bf802 | ||
|
|
7bc0e9db7c | ||
|
|
ca10bb01db | ||
|
|
c0f14b7c33 | ||
|
|
b096f328fc | ||
|
|
88dbd43884 | ||
|
|
ade5e4d4b8 | ||
|
|
cb76b53a1b | ||
|
|
b9423f70d4 | ||
|
|
d61e18e6f3 | ||
|
|
ca4a725a79 | ||
|
|
17a18d5fea | ||
|
|
73be238ac4 | ||
|
|
796a034fec | ||
|
|
952b320975 | ||
|
|
be0f06ff0f | ||
|
|
6294ef2db2 | ||
|
|
78b5d505bd | ||
|
|
22afdffa15 | ||
|
|
d1056eddeb | ||
|
|
8655f4d85a | ||
|
|
f9deb54352 | ||
|
|
eae3c1b33a | ||
|
|
5c5f8aa741 | ||
|
|
ba68ac2e32 | ||
|
|
239fef281e | ||
|
|
c0880b647f | ||
|
|
039dc6a76b | ||
|
|
042a7625ad | ||
|
|
41827c478d | ||
|
|
9a73180c3c | ||
|
|
333ee85fdb | ||
|
|
fa8ca45b6a | ||
|
|
c2bae1aeb7 | ||
|
|
ada7a45fe6 | ||
|
|
2d09df55a9 | ||
|
|
ec2554537e | ||
|
|
f266dfadc6 | ||
|
|
99048eed61 | ||
|
|
fe63718b0c | ||
|
|
2c1200433c | ||
|
|
4a3252c929 | ||
|
|
5e052174ee | ||
|
|
cda0966105 | ||
|
|
c0a9716846 | ||
|
|
18c7395f9d | ||
|
|
d3da79f3dd | ||
|
|
5a215daca4 | ||
|
|
8527a3b3ef | ||
|
|
492dc1ba32 | ||
|
|
305a8ca802 | ||
|
|
6d2a35494f | ||
|
|
e76a08c73a | ||
|
|
a0fef0c20f | ||
|
|
605432ddd1 | ||
|
|
0c895071d8 | ||
|
|
79cd833ae6 | ||
|
|
0fee928e37 | ||
|
|
7f698336d7 | ||
|
|
cef04f192a | ||
|
|
4087c4c226 | ||
|
|
87bdcd2372 | ||
|
|
1dbcc0d7c8 | ||
|
|
4c93f01c64 | ||
|
|
0a18412da0 | ||
|
|
b67d16bdc4 | ||
|
|
976542d355 | ||
|
|
a2e5fda646 | ||
|
|
41b1b65d5f | ||
|
|
2ec2d1997f | ||
|
|
0bc65f3b72 | ||
|
|
9ed96b3599 | ||
|
|
b1cf9566f6 | ||
|
|
13ea97bd98 | ||
|
|
009a17a9ca | ||
|
|
ca38d9adb1 | ||
|
|
4e5a86e3db | ||
|
|
983ae4f8c2 | ||
|
|
7ce3531cc7 | ||
|
|
11f1a7fd1c | ||
|
|
47d7dd4d22 | ||
|
|
8b3c0fd94e | ||
|
|
a736fe7989 | ||
|
|
1970b7f249 | ||
|
|
aa1f081664 |
119
CHANGELOG.md
119
CHANGELOG.md
@@ -6,6 +6,125 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.3.0] - 2022-02-27
|
||||
|
||||
- Fix parsing contour-level from emdb v3 header files
|
||||
- Fix invalid CSS (#376)
|
||||
- Fix "texture not renderable" & "texture not bound" warnings (#319)
|
||||
- Fix visual for bonds between two aromatic rings
|
||||
- Fix visual for delocalized bonds (parsed from mmcif and mol2)
|
||||
- Fix ring computation algorithm
|
||||
- Add ``UnitResonance`` property with info about delocalized triplets
|
||||
- Resolve marking in main renderer loop to improve overall performance
|
||||
- Use ``throttleTime`` instead of ``debounceTime`` in sequence viewer for better responsiveness
|
||||
- Change line geometry default ``scaleFactor`` to 2 (3 is too big after fixing line rendering)
|
||||
- Trajectory animation performance improvements
|
||||
- Reuse ``Model.CoarseGrained`` for coordinate trajectories
|
||||
- Avoid calculating ``InterUnitBonds`` when ``Structure.parent`` ones are empty
|
||||
- Reuse unit boundary if sphere has not changed too much
|
||||
- Don't show 'inter-bond' and 'element-cross' visuals in line representations of polymerAndLigand preset
|
||||
- Fix additional mononucleotides detected as polymer components
|
||||
- Fix and improve ``canRemap`` handling in ``IntraUnitBonds``
|
||||
- Reuse occlusion for secondary passes during multi-sampling
|
||||
- Check if marking passes are needed before doing them
|
||||
- Add ``resolutionScale`` parameter to allow trading quality of occlusion for performance
|
||||
|
||||
## [v3.2.0] - 2022-02-17
|
||||
|
||||
- Rename "best database mapping" to "SIFTS Mapping"
|
||||
- Add schema and export support for ``atom_site.pdbx_sifts_xref_*`` fields
|
||||
- Add schema export support for ``atom_site.pdbx_label_index`` field
|
||||
- Add `traceOnly` parameter to chain/UniProt-based structure alignment
|
||||
- Store ``IndexPairBonds`` as a dynamic property.
|
||||
|
||||
## [v3.1.0] - 2022-02-06
|
||||
|
||||
- Fix ``xrayShaded`` & ``ignoreLight`` params not working at the same time
|
||||
- Add ``ignoreLight`` to component params
|
||||
- Tweaks for cleaner default representation style
|
||||
- Cartoon: use ``nucleotide-ring`` instead of ``nucleotide-block``
|
||||
- Focus: use ``xrayShaded`` instead of opacity; adjust target size; don't show non-covalent interactions twice
|
||||
- Fix representation preset side effects (changing post-processing parameters, see #363)
|
||||
- Add Quick Styles panel (default, illustrative, stylized)
|
||||
- Fix exported structure missing secondary-structure categories (#364)
|
||||
- Fix volume streaming error message: distinguish between missing data and server error (#364)
|
||||
|
||||
## [v3.0.2] - 2022-01-30
|
||||
|
||||
- Fix color smoothing of elongated structures (by fixing ``Sphere.expand`` for spheres with highly directional extrema)
|
||||
- Fix entity label not displayed when multiple instances of the same entity are highlighted
|
||||
- Fix empty elements created in ``StructureElement.Loci.extendToAllInstances``
|
||||
- Measurement options tweaks (allow larger ``textSize``; make ``customText`` essential)
|
||||
- Fix visual visibility sync edge case when changing state snapshots
|
||||
|
||||
## [v3.0.1] - 2022-01-27
|
||||
|
||||
- Fix marking pass not working with ``transparentBackground``
|
||||
- Fix pdbe xray maps url not https
|
||||
- Fix entity-id color theme broken for non-IHM models
|
||||
- Improve/fix marking of ``InteractionsInterUnitVisual`` (mark when all contact-feature members are given)
|
||||
- Add missing "entity-id" and "enity-source" options for carbon coloring to "element-symbol" color theme
|
||||
- Fix VolumeServer/query CLI
|
||||
- Support automatic iso-value adjustment for VolumeServer data in ``Viewer.loadVolumeFromUrl``
|
||||
- Emit drag event whenever started within viewport (not only for non-empty loci)
|
||||
|
||||
## [v3.0.0] - 2022-01-23
|
||||
|
||||
- Assembly handling tweaks:
|
||||
- Do not include suffix for "identity assembly operators"
|
||||
- Do not include assembly-related categories to export if the structure was composed from an assembly
|
||||
- Special case for ``structAsymMap`` if Mol* asym id operator mapping is present
|
||||
- Support for opening ZIP files with multiple entries
|
||||
- Add Model Export extension
|
||||
- Bugfix: Automatically treat empty string as "non-present" value in BinaryCIF writer.
|
||||
- Fix coarse model support in entity-id color theme
|
||||
- Fix marking of carbohydrate visuals (whole chain could get marked instead of single residue)
|
||||
- Add custom colors to "element-symbol", "molecule-type", "residue-name", and "secondary-structure" themes
|
||||
- Support/bugfixes for ``atom_site.pdbx_sifts_xref`` categories
|
||||
- Improve/fix marking of ``InteractionsIntraUnitVisual`` (mark when all contact-feature members are given)
|
||||
|
||||
## [v3.0.0-dev.10] - 2022-01-17
|
||||
|
||||
- Fix ``getOperatorsForIndex``
|
||||
- Pass animation info (current frame & count) to state animations
|
||||
- Fix camera stutter for "camera spin" animation
|
||||
- Add formal charge parsing support for MOL/SDF files (thanks @ptourlas)
|
||||
- [Breaking] Cleaner looking ``MembraneOrientationVisuals`` defaults
|
||||
- [Breaking] Add rock animation to trackball controls
|
||||
- Add ``animate`` to ``TrackballControlsParams``, remove ``spin`` and ``spinSpeed``
|
||||
- Add ``animate`` to ``SimpleSettingsParams``, remove ``spin``
|
||||
- Add "camera rock" state animation
|
||||
- Add support for custom colors to "molecule-type" theme
|
||||
- [Breaking] Add style parameter to "illustrative" color theme
|
||||
- Defaults to "entity-id" style instead of "chain-id"
|
||||
- Add "illustrative" representation preset
|
||||
|
||||
## [v3.0.0-dev.9] - 2022-01-09
|
||||
|
||||
- Add PDBj as a ``pdb-provider`` option
|
||||
- Move Viewer APP to a separate file to allow use without importing light theme & index.html
|
||||
- Add symmetry support for mol2 files (only spacegroup setting 1)
|
||||
- Fix mol2 files element symbol assignment
|
||||
- Improve bond assignment from ``IndexPairBonds``
|
||||
- Add ``key`` field for mapping to source data
|
||||
- Fix assignment of bonds with unphysical length
|
||||
- Fix label/stats of single atom selection in multi-chain units
|
||||
|
||||
## [v3.0.0-dev.8] - 2021-12-31
|
||||
|
||||
- Add ``PluginFeatureDetection`` and disable WBOIT in Safari 15.
|
||||
- Add ``disable-wboit`` Viewer GET param
|
||||
- Add ``prefer-webgl1`` Viewer GET param
|
||||
- [Breaking] Refactor direct-volume rendering
|
||||
- Remove isosurface render-mode (use GPU MC instead)
|
||||
- Move coloring into theme (like for other geometries/renderables)
|
||||
- Add ``direct`` color type
|
||||
- Remove color from transfer-function (now only alpha)
|
||||
- Add direct-volume color theme support
|
||||
- Add volume-value color theme
|
||||
- [Breaking] Use size theme in molecular/gaussian surface & label representations
|
||||
- This is breaking because it was hardcoded to ``physical`` internally but the repr size theme default was ``uniform`` (now ``physical``)
|
||||
|
||||
## [v3.0.0-dev.7] - 2021-12-20
|
||||
|
||||
- Reduce number of created programs/shaders
|
||||
|
||||
72
CITATION.cff
Normal file
72
CITATION.cff
Normal file
@@ -0,0 +1,72 @@
|
||||
cff-version: 1.2.0
|
||||
title: >-
|
||||
Mol* library
|
||||
message: >-
|
||||
Please cite this software using the metadata from
|
||||
'preferred-citation'.
|
||||
authors:
|
||||
- given-names: Alexander S
|
||||
family-names: Rose
|
||||
orcid: 'https://orcid.org/0000-0002-0893-5551'
|
||||
- given-names: David
|
||||
family-names: Sehnal
|
||||
orcid: 'https://orcid.org/0000-0002-0682-3089'
|
||||
- given-names: Sebastian
|
||||
family-names: Bittrich
|
||||
orcid: 'https://orcid.org/0000-0003-3576-0387'
|
||||
- given-names: Áron Samuel
|
||||
family-names: Kovács
|
||||
- given-names: Ludovic
|
||||
family-names: Autin
|
||||
orcid: 'https://orcid.org/0000-0002-2197-191X'
|
||||
- given-names: Michal
|
||||
family-names: Malý
|
||||
- given-names: Jiří
|
||||
family-names: Černý
|
||||
- given-names: Panagiotis
|
||||
family-names: Tourlas
|
||||
type: software
|
||||
doi: 10.5281/zenodo.3947306
|
||||
preferred-citation:
|
||||
authors:
|
||||
- given-names: David
|
||||
family-names: Sehnal
|
||||
orcid: 'https://orcid.org/0000-0002-0682-3089'
|
||||
- given-names: Sebastian
|
||||
family-names: Bittrich
|
||||
orcid: 'https://orcid.org/0000-0003-3576-0387'
|
||||
- given-names: Mandar
|
||||
family-names: Deshpande
|
||||
orcid: 'https://orcid.org/0000-0002-9043-7665'
|
||||
- given-names: Radka
|
||||
family-names: Svobodová
|
||||
orcid: 'https://orcid.org/0000-0002-3840-8760'
|
||||
- given-names: Karel
|
||||
family-names: Berka
|
||||
orcid: 'https://orcid.org/0000-0001-9472-2589'
|
||||
- given-names: Václav
|
||||
family-names: Bazgier
|
||||
orcid: 'https://orcid.org/0000-0003-3393-3010'
|
||||
- given-names: Sameer
|
||||
family-names: Velankar
|
||||
orcid: 'https://orcid.org/0000-0002-8439-5964'
|
||||
- given-names: Stephen K
|
||||
family-names: Burley
|
||||
orcid: 'https://orcid.org/0000-0002-2487-9713'
|
||||
- given-names: Jaroslav
|
||||
family-names: Koča
|
||||
orcid: 'https://orcid.org/0000-0002-2780-4901'
|
||||
- given-names: Alexander S
|
||||
family-names: Rose
|
||||
orcid: 'https://orcid.org/0000-0002-0893-5551'
|
||||
title: >-
|
||||
Mol* Viewer: modern web app for 3D visualization
|
||||
and analysis of large biomolecular structures
|
||||
type: article
|
||||
doi: 10.1093/nar/gkab314
|
||||
journal: "Nucleic Acids Research"
|
||||
issue: W1
|
||||
volume: 49
|
||||
year: 2021
|
||||
month: 7
|
||||
pages: "W431–W437"
|
||||
@@ -11,6 +11,13 @@ When using Mol*, please cite:
|
||||
|
||||
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: [Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures](https://doi.org/10.1093/nar/gkab314), *Nucleic Acids Research*, 2021; https://doi.org/10.1093/nar/gkab314.
|
||||
|
||||
### Protein Data Bank Integrations
|
||||
|
||||
- The [pdbe-molstar](https://github.com/molstar/pdbe-molstar) library is the Mol* implementation used by EMBL-EBI data resources such as [PDBe](https://pdbe.org/), [PDBe-KB](https://pdbe-kb.org/) and [AlphaFold DB](https://alphafold.ebi.ac.uk/). This implementation can be used as a JS plugin and a Web component and supports property/attribute-based easy customisation. It provides helper methods to facilitate programmatic interactions between the web application and the 3D viewer. It also provides a superposition view for overlaying all the observed ligand molecules on representative protein conformations.
|
||||
|
||||
- [rcsb-molstar](https://github.com/molstar/rcsb-molstar) is the Mol* plugin used by [RCSB PDB](https://www.rcsb.org). The project provides additional presets for the visualization of structure alignments and structure motifs such as ligand binding sites. Furthermore, [rcsb-molstar](https://github.com/molstar/rcsb-molstar) allows to interactively add or hide of (parts of) chains, as seen in the [3D Protein Feature View](https://www.rcsb.org/3d-sequence/4hhb).
|
||||
|
||||
|
||||
## Project Structure Overview
|
||||
|
||||
The core of Mol* consists of these modules (see under `src/`):
|
||||
|
||||
@@ -24,6 +24,11 @@ atom_site.auth_asym_id
|
||||
atom_site.auth_seq_id
|
||||
atom_site.pdbx_PDB_model_num
|
||||
atom_site.ihm_model_id
|
||||
atom_site.pdbx_label_index
|
||||
atom_site.pdbx_sifts_xref_db_name
|
||||
atom_site.pdbx_sifts_xref_db_acc
|
||||
atom_site.pdbx_sifts_xref_db_num
|
||||
atom_site.pdbx_sifts_xref_db_res
|
||||
|
||||
atom_site_anisotrop.id
|
||||
atom_site_anisotrop.U
|
||||
|
||||
|
@@ -34,6 +34,14 @@
|
||||
* ACE (many, e.g. 5AGU, 1E1X)
|
||||
* ACY in 7ABY
|
||||
* NH2 (many, e.g. 6Y13)
|
||||
* Ligands with many rings
|
||||
* STU (e.g. 1U59) - many fused rings
|
||||
* HT (e.g. 127D) - rings connected by a single bond
|
||||
* J2C (e.g. 7EFJ) - rings connected by a single atom
|
||||
* RBF (e.g. 7QF2) - three linearly fused rings
|
||||
* TA1 (e.g. 1JFF) - many fused rings (incl. a 8-member rings)
|
||||
* BPA (e.g. 1JDG) - many fused rings
|
||||
* CLR (e.g. 3GKI) - four fused rings
|
||||
|
||||
Assembly symmetries
|
||||
* 5M30 (Assembly 1, C3 local and pseudo)
|
||||
|
||||
8850
package-lock.json
generated
8850
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
86
package.json
86
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.0.0-dev.7",
|
||||
"version": "3.3.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -13,7 +13,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"test": "npm install --no-save \"gl@^5.0.0\" && npm run lint && jest",
|
||||
"jest": "jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
@@ -86,77 +86,75 @@
|
||||
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.1.0",
|
||||
"@graphql-codegen/cli": "^2.3.0",
|
||||
"@graphql-codegen/time": "^3.1.0",
|
||||
"@graphql-codegen/typescript": "^2.4.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.1",
|
||||
"@graphql-codegen/add": "^3.1.1",
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/time": "^3.1.1",
|
||||
"@graphql-codegen/typescript": "^2.4.5",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.7",
|
||||
"@graphql-codegen/typescript-operations": "^2.3.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.5.0",
|
||||
"@typescript-eslint/parser": "^5.5.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.12.1",
|
||||
"@typescript-eslint/parser": "^5.12.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^6.4.0",
|
||||
"cpx2": "^4.0.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.3.0",
|
||||
"css-loader": "^6.6.0",
|
||||
"eslint": "^8.10.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"graphql": "^15.7.2",
|
||||
"http-server": "^14.0.0",
|
||||
"jest": "^27.3.1",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"fs-extra": "^10.0.1",
|
||||
"graphql": "^16.3.0",
|
||||
"http-server": "^14.1.0",
|
||||
"jest": "^27.5.1",
|
||||
"mini-css-extract-plugin": "^2.5.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.43.5",
|
||||
"sass-loader": "^12.3.0",
|
||||
"simple-git": "^2.47.0",
|
||||
"sass": "^1.49.9",
|
||||
"sass-loader": "^12.6.0",
|
||||
"simple-git": "^3.2.6",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^27.0.7",
|
||||
"typescript": "^4.5.2",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.5.5",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.10",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/node-fetch": "^2.6.1",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"body-parser": "^1.19.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.17.3",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.7",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"swagger-ui-dist": "^4.1.1",
|
||||
"immer": "^9.0.12",
|
||||
"immutable": "^4.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.4",
|
||||
"swagger-ui-dist": "^4.5.2",
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"gl": "^4.9.2"
|
||||
"react": "^17.0.2 || ^16.14.0",
|
||||
"react-dom": "^17.0.2 || ^16.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,11 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
samples: 64,
|
||||
radius: 8,
|
||||
bias: 1.0,
|
||||
blurKernelSize: 13
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
radius: 5,
|
||||
samples: 32,
|
||||
resolutionScale: 1
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
|
||||
491
src/apps/viewer/app.ts
Normal file
491
src/apps/viewer/app.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
const Extensions = {
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
export class Viewer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const definedOptions = {} as any;
|
||||
// filter for defined properies only so the default values
|
||||
// are property applied
|
||||
for (const p of Object.keys(options) as (keyof ViewerOptions)[]) {
|
||||
if (options[p] !== void 0) definedOptions[p] = options[p];
|
||||
}
|
||||
|
||||
const o: ViewerOptions = { ...DefaultViewerOptions, ...definedOptions };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
animations: [...defaultSpec.animations || []],
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
customFormats: o?.customFormats,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
regionState: {
|
||||
bottom: 'full',
|
||||
left: o.collapseLeftPanel ? 'collapsed' : 'full',
|
||||
right: o.collapseRightPanel ? 'hidden' : 'full',
|
||||
top: 'full',
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[PluginConfig.State.DefaultServer, o.pluginStateServer],
|
||||
[PluginConfig.State.CurrentServer, o.pluginStateServer],
|
||||
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
|
||||
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
|
||||
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
|
||||
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
|
||||
]
|
||||
};
|
||||
|
||||
const element = typeof elementOrId === 'string'
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
const plugin = await createPluginUI(element, spec, {
|
||||
onBeforeUIRender: plugin => {
|
||||
// the preset needs to be added before the UI renders otherwise
|
||||
// "Download Structure" wont be able to pick it up
|
||||
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
|
||||
}
|
||||
});
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
}
|
||||
|
||||
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
|
||||
}
|
||||
|
||||
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
|
||||
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
}
|
||||
|
||||
loadPdb(pdb: string, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'pdb' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdb,
|
||||
server: {
|
||||
name: provider,
|
||||
params: PdbDownloadProvider[provider].defaultValue as any
|
||||
}
|
||||
},
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadPdbDev(pdbDev: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'pdb-dev' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdbDev,
|
||||
encoding: 'bcif',
|
||||
},
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadEmdb(emdb: string, options?: { detail?: number }) {
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
|
||||
source: {
|
||||
name: 'pdb-emd-ds' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: emdb,
|
||||
server: provider,
|
||||
},
|
||||
detail: options?.detail ?? 3,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadAlphaFoldDb(afdb: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'alphafolddb' as const,
|
||||
params: {
|
||||
id: afdb,
|
||||
options: {
|
||||
...params.source.params.options,
|
||||
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadModelArchive(id: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'modelarchive' as const,
|
||||
params: {
|
||||
id,
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @example Load X-ray density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1.5,
|
||||
color: 0x3362B2
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: 3,
|
||||
color: 0x33BB33,
|
||||
volumeIndex: 1
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: -3,
|
||||
color: 0xBB3333,
|
||||
volumeIndex: 1
|
||||
}], {
|
||||
entryId: ['2FO-FC', 'FO-FC'],
|
||||
isLazy: true
|
||||
});
|
||||
* *********************
|
||||
* @example Load EM density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1,
|
||||
color: 0x3377aa
|
||||
}], {
|
||||
entryId: 'EMD-30210',
|
||||
isLazy: true
|
||||
});
|
||||
*/
|
||||
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!plugin.dataFormats.get(format)) {
|
||||
throw new Error(`Unknown density format: ${format}`);
|
||||
}
|
||||
|
||||
if (options?.isLazy) {
|
||||
const update = this.plugin.build();
|
||||
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
|
||||
url,
|
||||
format,
|
||||
entryId: options?.entryId,
|
||||
isBinary,
|
||||
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
|
||||
});
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
|
||||
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
|
||||
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const repr = plugin.build();
|
||||
for (const iso of isovalues) {
|
||||
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume;
|
||||
const volumeData = volume.cell!.obj!.data;
|
||||
repr
|
||||
.to(volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: Volume.adjustedIsoValue(volumeData, iso.value, iso.type) },
|
||||
color: 'uniform',
|
||||
colorParams: { value: iso.color }
|
||||
}));
|
||||
}
|
||||
|
||||
await repr.commit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* viewer.loadTrajectory({
|
||||
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
|
||||
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
|
||||
* preset: 'all-models' // or 'default'
|
||||
* });
|
||||
*/
|
||||
async loadTrajectory(params: LoadTrajectoryParams) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
let model: StateObjectSelector, coords: StateObjectSelector;
|
||||
|
||||
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
|
||||
const data = params.model.kind === 'model-data'
|
||||
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
|
||||
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
|
||||
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
|
||||
model = await plugin.builders.structure.createModel(trajectory);
|
||||
} else {
|
||||
const data = params.model.kind === 'topology-data'
|
||||
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
|
||||
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
|
||||
|
||||
const provider = plugin.dataFormats.get(params.model.format);
|
||||
model = await provider!.parse(plugin, data);
|
||||
}
|
||||
|
||||
{
|
||||
const data = params.coordinates.kind === 'coordinates-data'
|
||||
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
|
||||
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
|
||||
|
||||
const provider = plugin.dataFormats.get(params.coordinates.format);
|
||||
coords = await provider!.parse(plugin, data);
|
||||
}
|
||||
|
||||
const trajectory = await plugin.build().toRoot()
|
||||
.apply(TrajectoryFromModelAndCoordinates, {
|
||||
modelRef: model.ref,
|
||||
coordinatesRef: coords.ref
|
||||
}, { dependsOn: [model.ref, coords.ref] })
|
||||
.commit();
|
||||
|
||||
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
|
||||
|
||||
return { model, coords, preset };
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export interface LoadStructureOptions {
|
||||
representationParams?: StructureRepresentationPresetProvider.CommonParams
|
||||
}
|
||||
|
||||
export interface VolumeIsovalueInfo {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number,
|
||||
volumeIndex?: number
|
||||
}
|
||||
|
||||
export interface LoadTrajectoryParams {
|
||||
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
|
||||
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
|
||||
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
modelLabel?: string,
|
||||
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -56,6 +56,8 @@
|
||||
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
@@ -69,6 +71,8 @@
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
enableWboit: disableWboit ? true : void 0, // use default value if disable-wboit is not set
|
||||
preferWebgl1: preferWebgl1,
|
||||
}).then(viewer => {
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
@@ -1,482 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
const Extensions = {
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
export class Viewer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const o = { ...DefaultViewerOptions, ...options };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
animations: [...defaultSpec.animations || []],
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
customFormats: o?.customFormats,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
regionState: {
|
||||
bottom: 'full',
|
||||
left: o.collapseLeftPanel ? 'collapsed' : 'full',
|
||||
right: o.collapseRightPanel ? 'hidden' : 'full',
|
||||
top: 'full',
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[PluginConfig.State.DefaultServer, o.pluginStateServer],
|
||||
[PluginConfig.State.CurrentServer, o.pluginStateServer],
|
||||
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
|
||||
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
|
||||
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
|
||||
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
|
||||
]
|
||||
};
|
||||
|
||||
const element = typeof elementOrId === 'string'
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
const plugin = await createPluginUI(element, spec, {
|
||||
onBeforeUIRender: plugin => {
|
||||
// the preset needs to be added before the UI renders otherwise
|
||||
// "Download Structure" wont be able to pick it up
|
||||
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
|
||||
}
|
||||
});
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
}
|
||||
|
||||
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
|
||||
}
|
||||
|
||||
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
|
||||
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
}
|
||||
|
||||
loadPdb(pdb: string, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'pdb' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdb,
|
||||
server: {
|
||||
name: provider,
|
||||
params: PdbDownloadProvider[provider].defaultValue as any
|
||||
}
|
||||
},
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadPdbDev(pdbDev: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'pdb-dev' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdbDev,
|
||||
encoding: 'bcif',
|
||||
},
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadEmdb(emdb: string, options?: { detail?: number }) {
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
|
||||
source: {
|
||||
name: 'pdb-emd-ds' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: emdb,
|
||||
server: provider,
|
||||
},
|
||||
detail: options?.detail ?? 3,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadAlphaFoldDb(afdb: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'alphafolddb' as const,
|
||||
params: {
|
||||
id: afdb,
|
||||
options: {
|
||||
...params.source.params.options,
|
||||
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadModelArchive(id: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'modelarchive' as const,
|
||||
params: {
|
||||
id,
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @example Load X-ray density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1.5,
|
||||
color: 0x3362B2
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: 3,
|
||||
color: 0x33BB33,
|
||||
volumeIndex: 1
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: -3,
|
||||
color: 0xBB3333,
|
||||
volumeIndex: 1
|
||||
}], {
|
||||
entryId: ['2FO-FC', 'FO-FC'],
|
||||
isLazy: true
|
||||
});
|
||||
* *********************
|
||||
* @example Load EM density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1,
|
||||
color: 0x3377aa
|
||||
}], {
|
||||
entryId: 'EMD-30210',
|
||||
isLazy: true
|
||||
});
|
||||
*/
|
||||
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!plugin.dataFormats.get(format)) {
|
||||
throw new Error(`Unknown density format: ${format}`);
|
||||
}
|
||||
|
||||
if (options?.isLazy) {
|
||||
const update = this.plugin.build();
|
||||
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
|
||||
url,
|
||||
format,
|
||||
entryId: options?.entryId,
|
||||
isBinary,
|
||||
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
|
||||
});
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
|
||||
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
|
||||
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const repr = plugin.build();
|
||||
for (const iso of isovalues) {
|
||||
repr
|
||||
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
|
||||
color: 'uniform',
|
||||
colorParams: { value: iso.color }
|
||||
}));
|
||||
}
|
||||
|
||||
await repr.commit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* viewer.loadTrajectory({
|
||||
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
|
||||
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
|
||||
* preset: 'all-models' // or 'default'
|
||||
* });
|
||||
*/
|
||||
async loadTrajectory(params: LoadTrajectoryParams) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
let model: StateObjectSelector, coords: StateObjectSelector;
|
||||
|
||||
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
|
||||
const data = params.model.kind === 'model-data'
|
||||
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
|
||||
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
|
||||
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
|
||||
model = await plugin.builders.structure.createModel(trajectory);
|
||||
} else {
|
||||
const data = params.model.kind === 'topology-data'
|
||||
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
|
||||
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
|
||||
|
||||
const provider = plugin.dataFormats.get(params.model.format);
|
||||
model = await provider!.parse(plugin, data);
|
||||
}
|
||||
|
||||
{
|
||||
const data = params.coordinates.kind === 'coordinates-data'
|
||||
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
|
||||
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
|
||||
|
||||
const provider = plugin.dataFormats.get(params.coordinates.format);
|
||||
coords = await provider!.parse(plugin, data);
|
||||
}
|
||||
|
||||
const trajectory = await plugin.build().toRoot()
|
||||
.apply(TrajectoryFromModelAndCoordinates, {
|
||||
modelRef: model.ref,
|
||||
coordinatesRef: coords.ref
|
||||
}, { dependsOn: [model.ref, coords.ref] })
|
||||
.commit();
|
||||
|
||||
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
|
||||
|
||||
return { model, coords, preset };
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export interface LoadStructureOptions {
|
||||
representationParams?: StructureRepresentationPresetProvider.CommonParams
|
||||
}
|
||||
|
||||
export interface VolumeIsovalueInfo {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number,
|
||||
volumeIndex?: number
|
||||
}
|
||||
|
||||
export interface LoadTrajectoryParams {
|
||||
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
|
||||
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
|
||||
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
modelLabel?: string,
|
||||
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
export * from './app';
|
||||
|
||||
@@ -97,7 +97,7 @@ export class AlphaOrbitalsExample {
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
|
||||
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: true });
|
||||
|
||||
private selectors?: Selectors = void 0;
|
||||
private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;
|
||||
|
||||
@@ -74,12 +74,20 @@ class BasicWrapper {
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: props => {
|
||||
props.trackball.spin = !props.trackball.spin;
|
||||
settings: {
|
||||
trackball: {
|
||||
...trackball,
|
||||
animate: trackball.animate.name === 'spin'
|
||||
? { name: 'off', params: {} }
|
||||
: { name: 'spin', params: { speed: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
if (this.plugin.canvas3d.props.trackball.animate.name !== 'spin') {
|
||||
PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
}
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
|
||||
@@ -24,7 +24,7 @@ const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
|
||||
},
|
||||
renderer: {
|
||||
@@ -36,7 +36,7 @@ const Canvas3DPresets = {
|
||||
occlusion: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
|
||||
@@ -256,7 +256,16 @@ class MolStarProteopediaWrapper {
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...trackball,
|
||||
animate: trackball.animate.name === 'spin'
|
||||
? { name: 'off', params: {} }
|
||||
: { name: 'spin', params: { speed: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
viewport = {
|
||||
|
||||
@@ -43,9 +43,9 @@ export type BilayerPlanesProps = PD.Values<BilayerPlanesParams>
|
||||
const BilayerRimsParams = {
|
||||
...Lines.Params,
|
||||
...SharedParams,
|
||||
lineSizeAttenuation: PD.Boolean(true),
|
||||
linesSize: PD.Numeric(0.3, { min: 0.01, max: 50, step: 0.01 }),
|
||||
dashedLines: PD.Boolean(true),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
linesSize: PD.Numeric(0.5, { min: 0.01, max: 50, step: 0.01 }),
|
||||
dashedLines: PD.Boolean(false),
|
||||
};
|
||||
export type BilayerRimsParams = typeof BilayerRimsParams
|
||||
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
|
||||
|
||||
@@ -25,7 +25,7 @@ import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { decodeFloatRGB } from '../../mol-util/float-packing';
|
||||
import { unpackRGBToInt } from '../../mol-util/number-packing';
|
||||
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
|
||||
import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
|
||||
|
||||
@@ -65,7 +65,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
const r = tSize.array[i * 3];
|
||||
const g = tSize.array[i * 3 + 1];
|
||||
const b = tSize.array[i * 3 + 2];
|
||||
return decodeFloatRGB(r, g, b) / sizeDataFactor;
|
||||
return unpackRGBToInt(r, g, b) / sizeDataFactor;
|
||||
}
|
||||
|
||||
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
|
||||
@@ -95,9 +95,9 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
const g = groups[i4 + 1];
|
||||
const b = groups[i4 + 2];
|
||||
if (groups instanceof Float32Array) {
|
||||
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
|
||||
return unpackRGBToInt(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
|
||||
}
|
||||
return decodeFloatRGB(r, g, b);
|
||||
return unpackRGBToInt(r, g, b);
|
||||
}
|
||||
|
||||
protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
|
||||
|
||||
87
src/extensions/model-export/export.ts
Normal file
87
src/extensions/model-export/export.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
|
||||
import { 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';
|
||||
|
||||
export async function exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
|
||||
try {
|
||||
await plugin.runTask(_exportHierarchy(plugin, options), { useOverlay: true });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(`Model export failed. See console for details.`);
|
||||
}
|
||||
}
|
||||
|
||||
function _exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
|
||||
return Task.create('Export', async ctx => {
|
||||
await ctx.update({ message: 'Exporting...', isIndeterminate: true, canAbort: false });
|
||||
|
||||
const format = options?.format ?? 'cif';
|
||||
const { structures } = plugin.managers.structure.hierarchy.current;
|
||||
|
||||
const files: [name: string, data: string | Uint8Array][] = [];
|
||||
const entryMap = new Map<string, number>();
|
||||
|
||||
for (const _s of structures) {
|
||||
const s = _s.transform?.cell.obj?.data ?? _s.cell.obj?.data;
|
||||
if (!s) continue;
|
||||
if (s.models.length > 1) {
|
||||
plugin.log.warn(`[Export] Skipping ${_s.cell.obj?.label}: Multimodel exports not supported.`);
|
||||
continue;
|
||||
}
|
||||
if (s.units.some(u => !Unit.isAtomic(u))) {
|
||||
plugin.log.warn(`[Export] Skipping ${_s.cell.obj?.label}: Non-atomic model exports not supported.`);
|
||||
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);
|
||||
|
||||
await ctx.update({ message: `Exporting ${s.model.entryId}...`, 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 })]);
|
||||
} 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.`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length === 1) {
|
||||
download(new Blob([files[0][1]]), files[0][0]);
|
||||
} else if (files.length > 1) {
|
||||
const zipData: Record<string, Uint8Array> = {};
|
||||
for (const [fn, data] of files) {
|
||||
if (data instanceof Uint8Array) {
|
||||
zipData[fn] = data;
|
||||
} else {
|
||||
const bytes = new Uint8Array(utf8ByteCount(data));
|
||||
utf8Write(bytes, 0, data);
|
||||
zipData[fn] = bytes;
|
||||
}
|
||||
}
|
||||
await ctx.update({ message: `Compressing Data...`, isIndeterminate: true, canAbort: false });
|
||||
const buffer = await zip(ctx, zipData);
|
||||
download(new Blob([new Uint8Array(buffer, 0, buffer.byteLength)]), `structures_${getFormattedTime()}.zip`);
|
||||
}
|
||||
|
||||
plugin.log.info(`[Export] Done.`);
|
||||
});
|
||||
}
|
||||
30
src/extensions/model-export/index.ts
Normal file
30
src/extensions/model-export/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { ModelExportUI } from './ui';
|
||||
|
||||
export const ModelExport = PluginBehavior.create<{}>({
|
||||
name: 'extension-model-export',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'Model Export'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{}> {
|
||||
register(): void {
|
||||
this.ctx.customStructureControls.set('model-export', ModelExportUI as any);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.customStructureControls.delete('model-export');
|
||||
}
|
||||
},
|
||||
params: () => ({})
|
||||
});
|
||||
69
src/extensions/model-export/ui.tsx
Normal file
69
src/extensions/model-export/ui.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { exportHierarchy } from './export';
|
||||
|
||||
export class ModelExportUI extends CollapsableControls<{}, {}> {
|
||||
protected defaultState(): CollapsableState {
|
||||
return {
|
||||
header: 'Export Models',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: GetAppSvg }
|
||||
};
|
||||
}
|
||||
protected renderControls(): JSX.Element | null {
|
||||
return <ExportControls plugin={this.plugin} />;
|
||||
}
|
||||
}
|
||||
|
||||
const Params = {
|
||||
format: PD.Select<'cif' | 'bcif'>('cif', [['cif', 'mmCIF'], ['bcif', 'Binary mmCIF']])
|
||||
};
|
||||
const DefaultParams = PD.getDefaultValues(Params);
|
||||
|
||||
function ExportControls({ plugin }: { plugin: PluginContext }) {
|
||||
const [params, setParams] = useState(DefaultParams);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
useBehavior(plugin.managers.structure.hierarchy.behaviors.selection); // triggers UI update
|
||||
const isBusy = useBehavior(plugin.behaviors.state.isBusy);
|
||||
const hierarchy = plugin.managers.structure.hierarchy.current;
|
||||
|
||||
let label: string = 'Nothing to Export';
|
||||
if (hierarchy.structures.length === 1) {
|
||||
label = 'Export';
|
||||
} if (hierarchy.structures.length > 1) {
|
||||
label = 'Export (as ZIP)';
|
||||
}
|
||||
|
||||
const onExport = async () => {
|
||||
setExporting(true);
|
||||
try {
|
||||
await exportHierarchy(plugin, { format: params.format });
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return <>
|
||||
<ParameterControls params={Params} values={params} onChangeValues={setParams} isDisabled={isBusy || exporting} />
|
||||
<Button
|
||||
onClick={onExport}
|
||||
style={{ marginTop: 1 }}
|
||||
disabled={isBusy || hierarchy.structures.length === 0 || exporting}
|
||||
commit={hierarchy.structures.length ? 'on' : 'off'}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</>;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin:
|
||||
await plugin.managers.animation.play(params.animation.definition, params.animation.params);
|
||||
stoppedAnimation = false;
|
||||
for (let i = 0; i <= N; i++) {
|
||||
await loop.tick(i * dt, { isSynchronous: true, manualDraw: true });
|
||||
await loop.tick(i * dt, { isSynchronous: true, animation: { currentFrame: i, frameCount: N }, manualDraw: true });
|
||||
|
||||
const image = params.pass.getImageData(width, height, normalizedViewport);
|
||||
encoder.addFrameRgba(image.data);
|
||||
|
||||
@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
// Generated on 2021-11-25T14:34:23-08:00
|
||||
// Generated on 2022-02-27T12:49:36-08:00
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
@@ -477,6 +477,8 @@ export type ClustersMembers = {
|
||||
export type CoreAssembly = {
|
||||
/** Get PDB entry that includes this assembly. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
/** Get all pairwise polymer interfaces for this PDB assembly. */
|
||||
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
|
||||
readonly pdbx_struct_assembly?: Maybe<PdbxStructAssembly>;
|
||||
readonly pdbx_struct_assembly_auth_evidence?: Maybe<ReadonlyArray<Maybe<PdbxStructAssemblyAuthEvidence>>>;
|
||||
readonly pdbx_struct_assembly_gen?: Maybe<ReadonlyArray<Maybe<PdbxStructAssemblyGen>>>;
|
||||
@@ -724,6 +726,16 @@ export type CoreEntry = {
|
||||
readonly symmetry?: Maybe<Symmetry>;
|
||||
};
|
||||
|
||||
export type CoreInterface = {
|
||||
readonly rcsb_id: Scalars['String'];
|
||||
readonly rcsb_interface_container_identifiers: RcsbInterfaceContainerIdentifiers;
|
||||
readonly rcsb_interface_info?: Maybe<RcsbInterfaceInfo>;
|
||||
/** List of operations for each interface partner. */
|
||||
readonly rcsb_interface_operator: ReadonlyArray<Maybe<ReadonlyArray<Maybe<ReadonlyArray<Maybe<Scalars['String']>>>>>>;
|
||||
readonly rcsb_interface_partner: ReadonlyArray<Maybe<RcsbInterfacePartner>>;
|
||||
readonly rcsb_latest_revision?: Maybe<RcsbLatestRevision>;
|
||||
};
|
||||
|
||||
export type CoreNonpolymerEntity = {
|
||||
/** Get PDB entry that contains this non-polymer entity. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
@@ -3186,6 +3198,28 @@ export type GeneName = {
|
||||
readonly value?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type InterfacePartnerFeatureAdditionalProperties = {
|
||||
/**
|
||||
* The additional property name.
|
||||
*
|
||||
* Allowable values:
|
||||
* TO_BE_DEFINED
|
||||
*
|
||||
*/
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
/** The value(s) of the additional property. */
|
||||
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['ObjectScalar']>>>;
|
||||
};
|
||||
|
||||
export type InterfacePartnerFeatureFeaturePositions = {
|
||||
/** An identifier for the monomer at which this segment of the feature begins. */
|
||||
readonly beg_seq_id: Scalars['Int'];
|
||||
/** An identifier for the monomer at which this segment of the feature ends. */
|
||||
readonly end_seq_id?: Maybe<Scalars['Int']>;
|
||||
/** The value(s) of the feature over the monomer segment. */
|
||||
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
||||
};
|
||||
|
||||
export type PdbxAuditRevisionCategory = {
|
||||
/**
|
||||
* The category updated in the pdbx_audit_revision_category record.
|
||||
@@ -6745,6 +6779,10 @@ export type Query = {
|
||||
readonly entries?: Maybe<ReadonlyArray<Maybe<CoreEntry>>>;
|
||||
/** Get PDB entry given the PDB id. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
/** Get a pairwise polymeric interface given the PDB ID, ASSEMBLY ID and INTERFACE ID. */
|
||||
readonly interface?: Maybe<CoreInterface>;
|
||||
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. */
|
||||
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
|
||||
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
|
||||
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
|
||||
/** Get a PDB non-polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
|
||||
@@ -6831,6 +6869,20 @@ export type QueryEntryArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryInterfaceArgs = {
|
||||
assembly_id: Scalars['String'];
|
||||
entry_id: Scalars['String'];
|
||||
interface_id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryInterfacesArgs = {
|
||||
interface_ids: ReadonlyArray<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryNonpolymer_EntitiesArgs = {
|
||||
entity_ids: ReadonlyArray<Scalars['String']>;
|
||||
@@ -6952,6 +7004,8 @@ export type RcsbAssemblyContainerIdentifiers = {
|
||||
readonly assembly_id: Scalars['String'];
|
||||
/** Entry identifier for the container. */
|
||||
readonly entry_id: Scalars['String'];
|
||||
/** List of binary interface Ids within the assembly (it points to interface id collection). */
|
||||
readonly interface_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
|
||||
/**
|
||||
* A unique identifier for each object in this assembly container formed by
|
||||
* a dash separated concatenation of entry and assembly identifiers.
|
||||
@@ -7010,6 +7064,24 @@ export type RcsbAssemblyInfo = {
|
||||
* This is the total count of non-polymer entity instances generated in the assembly coordinate data.
|
||||
*/
|
||||
readonly nonpolymer_entity_instance_count?: Maybe<Scalars['Int']>;
|
||||
/** Number of heterologous (both binding sites are different) interface entities */
|
||||
readonly num_heterologous_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of heteromeric (both partners are different polymeric entities) interface entities */
|
||||
readonly num_heteromeric_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of homomeric (both partners are the same polymeric entity) interface entities */
|
||||
readonly num_homomeric_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of polymer-polymer interface entities, grouping equivalent interfaces at the entity level (i.e. same entity_ids on either side, with similar but not identical binding sites) */
|
||||
readonly num_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of geometrically equivalent (i.e. same asym_ids on either side) polymer-polymer interfaces in the assembly */
|
||||
readonly num_interfaces?: Maybe<Scalars['Int']>;
|
||||
/** Number of isologous (both binding sites are same, i.e. interface is symmetric) interface entities */
|
||||
readonly num_isologous_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of nucleic acid-nucleic acid interface entities */
|
||||
readonly num_na_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of protein-nucleic acid interface entities */
|
||||
readonly num_prot_na_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of protein-protein interface entities */
|
||||
readonly num_protein_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** The assembly non-hydrogen polymer entity atomic coordinate count. */
|
||||
readonly polymer_atom_count?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
@@ -7085,6 +7157,10 @@ export type RcsbAssemblyInfo = {
|
||||
* This is the total count of solvent entity instances generated in the assembly coordinate data.
|
||||
*/
|
||||
readonly solvent_entity_instance_count?: Maybe<Scalars['Int']>;
|
||||
/** Total buried surface area calculated as the sum of buried surface areas over all interfaces */
|
||||
readonly total_assembly_buried_surface_area?: Maybe<Scalars['Float']>;
|
||||
/** Total number of interfacing residues in the assembly, calculated as the sum of interfacing residues over all interfaces */
|
||||
readonly total_number_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
* The number of unmodeled polymer monomers in the assembly coordinate data. This is
|
||||
* the total count of monomers with unreported coordinate data for all polymer
|
||||
@@ -8817,6 +8893,99 @@ export type RcsbGenomicLineage = {
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfaceContainerIdentifiers = {
|
||||
/** This item references an assembly in pdbx_struct_assembly */
|
||||
readonly assembly_id: Scalars['String'];
|
||||
/** Entry identifier for the container. */
|
||||
readonly entry_id: Scalars['String'];
|
||||
/**
|
||||
* Identifier for NCS-equivalent interfaces within the assembly (same entity_ids on both sides)
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2
|
||||
*
|
||||
*/
|
||||
readonly interface_entity_id?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Identifier for the geometrically equivalent (same asym_ids on either side) interfaces within the assembly
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2
|
||||
*
|
||||
*/
|
||||
readonly interface_id: Scalars['String'];
|
||||
/**
|
||||
* Unique identifier for the document
|
||||
*
|
||||
* Examples:
|
||||
* 2UZI-1.A.B?1
|
||||
*
|
||||
*/
|
||||
readonly rcsb_id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RcsbInterfaceInfo = {
|
||||
/** Total interface buried surface area */
|
||||
readonly interface_area?: Maybe<Scalars['Float']>;
|
||||
/** Allowable values: homo, hetero. */
|
||||
readonly interface_character?: Maybe<Scalars['String']>;
|
||||
/** Number of core interface residues, defined as those that bury >90% accessible surface area with respect to the unbound state */
|
||||
readonly num_core_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/** Number of interface residues, defined as those with burial fraction > 0 */
|
||||
readonly num_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/** Allowable values: Nucleic acid (only), Protein (only), Protein/NA. */
|
||||
readonly polymer_composition?: Maybe<Scalars['String']>;
|
||||
/** The Jaccard score (intersection over union) of interface contacts in homomeric interfaces, comparing contact sets left-right vs right-left. High values indicate isologous (symmetric) interfaces, with value=1 if perfectly symmetric (e.g. crystallographic symmetry) */
|
||||
readonly self_jaccard_contact_score?: Maybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartner = {
|
||||
readonly interface_partner_feature?: Maybe<ReadonlyArray<Maybe<RcsbInterfacePartnerInterfacePartnerFeature>>>;
|
||||
readonly interface_partner_identifier?: Maybe<RcsbInterfacePartnerInterfacePartnerIdentifier>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartnerInterfacePartnerFeature = {
|
||||
readonly additional_properties?: Maybe<ReadonlyArray<Maybe<InterfacePartnerFeatureAdditionalProperties>>>;
|
||||
/**
|
||||
* Identifies the version of the feature assignment.
|
||||
*
|
||||
* Examples:
|
||||
* V4_0_2
|
||||
*
|
||||
*/
|
||||
readonly assignment_version?: Maybe<Scalars['String']>;
|
||||
/** A description for the feature. */
|
||||
readonly description?: Maybe<Scalars['String']>;
|
||||
/** An identifier for the feature. */
|
||||
readonly feature_id?: Maybe<Scalars['String']>;
|
||||
readonly feature_positions?: Maybe<ReadonlyArray<Maybe<InterfacePartnerFeatureFeaturePositions>>>;
|
||||
/** A name for the feature. */
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Code identifying the individual, organization or program that assigned the feature.
|
||||
*
|
||||
* Examples:
|
||||
* NACCESS
|
||||
*
|
||||
*/
|
||||
readonly provenance_source?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* ASA_UNBOUND, ASA_BOUND
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartnerInterfacePartnerIdentifier = {
|
||||
/** Instance identifier for this container. */
|
||||
readonly asym_id: Scalars['String'];
|
||||
/** Polymer entity identifier for the container. */
|
||||
readonly entity_id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RcsbLatestRevision = {
|
||||
/** The major version number of the latest revision. */
|
||||
readonly major_revision?: Maybe<Scalars['Int']>;
|
||||
@@ -10263,7 +10432,7 @@ export type RcsbPolymerInstanceFeature = {
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
|
||||
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
@@ -11113,11 +11282,11 @@ export type RcsbUniprotAlignments = {
|
||||
|
||||
export type RcsbUniprotAlignmentsCoreEntityAlignments = {
|
||||
/** Aligned region */
|
||||
readonly aligned_regions?: Maybe<ReadonlyArray<Maybe<CoreEntityAlignmentsAlignedRegions>>>;
|
||||
readonly aligned_regions: ReadonlyArray<Maybe<CoreEntityAlignmentsAlignedRegions>>;
|
||||
/** core_entity identifiers */
|
||||
readonly core_entity_identifiers?: Maybe<CoreEntityAlignmentsCoreEntityIdentifiers>;
|
||||
/** Alignment scores */
|
||||
readonly scores?: Maybe<CoreEntityAlignmentsScores>;
|
||||
readonly scores: CoreEntityAlignmentsScores;
|
||||
};
|
||||
|
||||
export type RcsbUniprotAnnotation = {
|
||||
@@ -13145,4 +13314,4 @@ export type AssemblySymmetryQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null | undefined>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null | undefined, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null | undefined> | null | undefined } | null | undefined> } | null | undefined>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null | undefined, readonly start: ReadonlyArray<number | null | undefined>, readonly end: ReadonlyArray<number | null | undefined> } | null | undefined> | null | undefined } | null | undefined> | null | undefined } | null | undefined };
|
||||
export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null> | null } | null> } | null>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null, readonly start: ReadonlyArray<number | null>, readonly end: ReadonlyArray<number | null> } | null> | null } | null> | null } | null };
|
||||
|
||||
@@ -223,7 +223,7 @@ interface Canvas3D {
|
||||
clear(): void
|
||||
syncVisibility(): void
|
||||
|
||||
requestDraw(force?: boolean): void
|
||||
requestDraw(): void
|
||||
|
||||
/** Reset the timers, used by "animate" */
|
||||
resetTime(t: number): void
|
||||
@@ -236,7 +236,7 @@ interface Canvas3D {
|
||||
/** Sets drawPaused = false without starting the built in animation loop */
|
||||
resume(): void
|
||||
identify(x: number, y: number): PickData | undefined
|
||||
mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void
|
||||
mark(loci: Representation.Loci, action: MarkerAction): void
|
||||
getLoci(pickingId: PickingId | undefined): Representation.Loci
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
@@ -345,7 +345,30 @@ namespace Canvas3D {
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) {
|
||||
let markBuffer: [reprLoci: Representation.Loci, action: MarkerAction][] = [];
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
// NOTE: might try to optimize a case with opposite actions for the
|
||||
// same loci. Tho this might end up being more expensive (and error prone)
|
||||
// then just applying everything "naively".
|
||||
markBuffer.push([reprLoci, action]);
|
||||
}
|
||||
|
||||
function resolveMarking() {
|
||||
let changed = false;
|
||||
for (const [r, l] of markBuffer) {
|
||||
changed = applyMark(r, l) || changed;
|
||||
}
|
||||
markBuffer = [];
|
||||
if (changed) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function applyMark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
@@ -355,14 +378,7 @@ namespace Canvas3D {
|
||||
changed = helper.camera.mark(loci, action) || changed;
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed && !noDraw) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
const prevPickDirty = pickHelper.dirty;
|
||||
draw(true);
|
||||
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function render(force: boolean) {
|
||||
@@ -379,6 +395,8 @@ namespace Canvas3D {
|
||||
y > gl.drawingBufferHeight || y + height < 0
|
||||
) return false;
|
||||
|
||||
const markingUpdated = resolveMarking();
|
||||
|
||||
let didRender = false;
|
||||
controls.update(currentTime);
|
||||
const cameraChanged = camera.update();
|
||||
@@ -386,25 +404,24 @@ namespace Canvas3D {
|
||||
const shouldRender = force || cameraChanged || resized || forceNextRender;
|
||||
forceNextRender = false;
|
||||
|
||||
const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample);
|
||||
const multiSampleChanged = multiSampleHelper.update(markingUpdated || shouldRender, p.multiSample);
|
||||
|
||||
if (shouldRender || multiSampleChanged) {
|
||||
if (shouldRender || multiSampleChanged || markingUpdated) {
|
||||
let cam: Camera | StereoCamera = camera;
|
||||
if (p.camera.stereo.name === 'on') {
|
||||
stereoCamera.update();
|
||||
cam = stereoCamera;
|
||||
}
|
||||
|
||||
const ctx = { renderer, camera: cam, scene, helper };
|
||||
if (MultiSamplePass.isEnabled(p.multiSample)) {
|
||||
if (!cameraChanged) {
|
||||
while (!multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p));
|
||||
} else {
|
||||
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
|
||||
}
|
||||
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
|
||||
multiSampleHelper.render(ctx, p, true, forceOn);
|
||||
} else {
|
||||
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking);
|
||||
passes.draw.render(ctx, p, true);
|
||||
}
|
||||
pickHelper.dirty = true;
|
||||
// if only marking has updated, do not set the flag to dirty
|
||||
pickHelper.dirty = pickHelper.dirty || shouldRender;
|
||||
didRender = true;
|
||||
}
|
||||
|
||||
@@ -416,15 +433,15 @@ namespace Canvas3D {
|
||||
let currentTime = 0;
|
||||
let drawPaused = false;
|
||||
|
||||
function draw(force?: boolean) {
|
||||
function draw(options?: { force?: boolean }) {
|
||||
if (drawPaused) return;
|
||||
if (render(!!force) && notifyDidDraw) {
|
||||
if (render(!!options?.force) && notifyDidDraw) {
|
||||
didDraw.next(now() - startTime as now.Timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
function requestDraw(force?: boolean) {
|
||||
forceNextRender = forceNextRender || !!force;
|
||||
function requestDraw() {
|
||||
forceNextRender = true;
|
||||
}
|
||||
|
||||
let animationFrameHandle = 0;
|
||||
@@ -438,7 +455,7 @@ namespace Canvas3D {
|
||||
return;
|
||||
}
|
||||
|
||||
draw(false);
|
||||
draw();
|
||||
if (!camera.transition.inTransition && !webgl.isContextLost) {
|
||||
interactionHelper.tick(currentTime);
|
||||
}
|
||||
@@ -478,7 +495,7 @@ namespace Canvas3D {
|
||||
resolveCameraReset();
|
||||
if (forceDrawAfterAllCommited) {
|
||||
if (helper.debug.isEnabled) helper.debug.update();
|
||||
draw(true);
|
||||
draw({ force: true });
|
||||
forceDrawAfterAllCommited = false;
|
||||
}
|
||||
commited.next(now());
|
||||
@@ -661,11 +678,11 @@ namespace Canvas3D {
|
||||
|
||||
const contextRestoredSub = contextRestored.subscribe(() => {
|
||||
pickHelper.dirty = true;
|
||||
draw(true);
|
||||
draw({ force: true });
|
||||
// Unclear why, but in Chrome with wboit enabled the first `draw` only clears
|
||||
// the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
|
||||
// context loss so it is unclear if it behaves the same.
|
||||
draw(true);
|
||||
draw({ force: true });
|
||||
});
|
||||
|
||||
const resized = new BehaviorSubject<any>(0);
|
||||
@@ -674,7 +691,7 @@ namespace Canvas3D {
|
||||
passes.updateSize();
|
||||
updateViewport();
|
||||
syncViewport();
|
||||
if (draw) requestDraw(true);
|
||||
if (draw) requestDraw();
|
||||
resized.next(+new Date());
|
||||
}
|
||||
|
||||
@@ -699,7 +716,7 @@ namespace Canvas3D {
|
||||
reprRenderObjects.clear();
|
||||
scene.clear();
|
||||
helper.debug.clear();
|
||||
requestDraw(true);
|
||||
requestDraw();
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
},
|
||||
syncVisibility: () => {
|
||||
@@ -711,7 +728,7 @@ namespace Canvas3D {
|
||||
if (scene.syncVisibility()) {
|
||||
if (helper.debug.isEnabled) helper.debug.update();
|
||||
}
|
||||
requestDraw(true);
|
||||
requestDraw();
|
||||
},
|
||||
|
||||
requestDraw,
|
||||
@@ -799,7 +816,7 @@ namespace Canvas3D {
|
||||
}
|
||||
|
||||
if (!doNotRequestDraw) {
|
||||
requestDraw(true);
|
||||
requestDraw();
|
||||
}
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
@@ -826,6 +843,8 @@ namespace Canvas3D {
|
||||
dispose: () => {
|
||||
contextRestoredSub.unsubscribe();
|
||||
|
||||
markBuffer = [];
|
||||
|
||||
scene.clear();
|
||||
helper.debug.clear();
|
||||
controls.dispose();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -13,7 +13,7 @@ import { Viewport } from '../camera/util';
|
||||
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Camera } from '../camera';
|
||||
import { absMax } from '../../mol-math/misc';
|
||||
import { absMax, degToRad } from '../../mol-math/misc';
|
||||
import { Binding } from '../../mol-util/binding';
|
||||
|
||||
const B = ButtonsType;
|
||||
@@ -40,8 +40,16 @@ export const TrackballControlsParams = {
|
||||
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
|
||||
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
|
||||
|
||||
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
|
||||
spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
|
||||
animate: PD.MappedStatic('off', {
|
||||
off: PD.EmptyGroup(),
|
||||
spin: PD.Group({
|
||||
speed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
|
||||
}, { description: 'Spin the 3D scene around the x-axis in view space' }),
|
||||
rock: PD.Group({
|
||||
speed: PD.Numeric(0.3, { min: -5, max: 5, step: 0.1 }),
|
||||
angle: PD.Numeric(10, { min: 0, max: 90, step: 1 }, { description: 'How many degrees to rotate in each direction.' }),
|
||||
}, { description: 'Rock the 3D scene around the x-axis in view space' })
|
||||
}),
|
||||
|
||||
staticMoving: PD.Boolean(true, { isHidden: true }),
|
||||
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
|
||||
@@ -72,7 +80,8 @@ export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
|
||||
|
||||
export { TrackballControls };
|
||||
interface TrackballControls {
|
||||
viewport: Viewport
|
||||
readonly viewport: Viewport
|
||||
readonly isAnimating: boolean
|
||||
|
||||
readonly props: Readonly<TrackballControlsProps>
|
||||
setProps: (props: Partial<TrackballControlsProps>) => void
|
||||
@@ -144,6 +153,11 @@ namespace TrackballControls {
|
||||
);
|
||||
}
|
||||
|
||||
function getRotateFactor() {
|
||||
const aspectRatio = input.width / input.height;
|
||||
return p.rotateSpeed * input.pixelRatio * aspectRatio;
|
||||
}
|
||||
|
||||
const rotAxis = Vec3();
|
||||
const rotQuat = Quat();
|
||||
const rotEyeDir = Vec3();
|
||||
@@ -156,8 +170,7 @@ namespace TrackballControls {
|
||||
const dy = _rotCurr[1] - _rotPrev[1];
|
||||
Vec3.set(rotMoveDir, dx, dy, 0);
|
||||
|
||||
const aspectRatio = input.width / input.height;
|
||||
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
|
||||
const angle = Vec3.magnitude(rotMoveDir) * getRotateFactor();
|
||||
|
||||
if (angle) {
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
@@ -306,7 +319,10 @@ namespace TrackballControls {
|
||||
/** Update the object's position, direction and up vectors */
|
||||
function update(t: number) {
|
||||
if (lastUpdated === t) return;
|
||||
if (p.spin && lastUpdated > 0) spin(t - lastUpdated);
|
||||
if (lastUpdated > 0) {
|
||||
if (p.animate.name === 'spin') spin(t - lastUpdated);
|
||||
else if (p.animate.name === 'rock') rock(t - lastUpdated);
|
||||
}
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
@@ -345,6 +361,7 @@ namespace TrackballControls {
|
||||
if (!isStart && !_isInteracting) return;
|
||||
|
||||
_isInteracting = true;
|
||||
resetRock(); // start rocking from the center after interactions
|
||||
|
||||
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
|
||||
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
|
||||
@@ -434,11 +451,34 @@ namespace TrackballControls {
|
||||
|
||||
const _spinSpeed = Vec2.create(0.005, 0);
|
||||
function spin(deltaT: number) {
|
||||
if (p.spinSpeed === 0) return;
|
||||
if (p.animate.name !== 'spin' || p.animate.params.speed === 0 || _isInteracting) return;
|
||||
|
||||
const frameSpeed = (p.spinSpeed || 0) / 1000;
|
||||
const frameSpeed = p.animate.params.speed / 1000;
|
||||
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
|
||||
if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
|
||||
Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
|
||||
}
|
||||
|
||||
let _rockPhase = 0;
|
||||
const _rockSpeed = Vec2.create(0.005, 0);
|
||||
function rock(deltaT: number) {
|
||||
if (p.animate.name !== 'rock' || p.animate.params.speed === 0 || _isInteracting) return;
|
||||
|
||||
const dt = deltaT / 1000 * p.animate.params.speed;
|
||||
const maxAngle = degToRad(p.animate.params.angle) / getRotateFactor();
|
||||
const angleA = Math.sin(_rockPhase * Math.PI * 2) * maxAngle;
|
||||
const angleB = Math.sin((_rockPhase + dt) * Math.PI * 2) * maxAngle;
|
||||
|
||||
_rockSpeed[0] = angleB - angleA;
|
||||
Vec2.add(_rotCurr, _rotPrev, _rockSpeed);
|
||||
|
||||
_rockPhase += dt;
|
||||
if (_rockPhase >= 1) {
|
||||
_rockPhase = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function resetRock() {
|
||||
_rockPhase = 0;
|
||||
}
|
||||
|
||||
function start(t: number) {
|
||||
@@ -448,9 +488,13 @@ namespace TrackballControls {
|
||||
|
||||
return {
|
||||
viewport,
|
||||
get isAnimating() { return p.animate.name !== 'off'; },
|
||||
|
||||
get props() { return p as Readonly<TrackballControlsProps>; },
|
||||
setProps: (props: Partial<TrackballControlsProps>) => {
|
||||
if (props.animate?.name === 'rock' && p.animate.name !== 'rock') {
|
||||
resetRock(); // start rocking from the center
|
||||
}
|
||||
Object.assign(p, props);
|
||||
},
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ export class Canvas3dInteractionHelper {
|
||||
const xyChanged = this.startX !== this.endX || this.startY !== this.endY;
|
||||
|
||||
if (e === InputEvent.Drag) {
|
||||
if (xyChanged && !Representation.Loci.isEmpty(this.prevLoci)) {
|
||||
if (xyChanged && !this.outsideViewport(this.startX, this.startY)) {
|
||||
this.events.drag.next({ current: this.prevLoci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, pageStart: Vec2.create(this.startX, this.startY), pageEnd: Vec2.create(this.endX, this.endY) });
|
||||
|
||||
this.startX = this.endX;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -53,6 +53,19 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
postprocessing: PostprocessingProps
|
||||
marking: MarkingProps
|
||||
transparentBackground: boolean;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
renderer: Renderer;
|
||||
camera: Camera | StereoCamera;
|
||||
scene: Scene;
|
||||
helper: Helper;
|
||||
}
|
||||
|
||||
export class DrawPass {
|
||||
private readonly drawTarget: RenderTarget;
|
||||
|
||||
@@ -264,25 +277,25 @@ export class DrawPass {
|
||||
renderer.renderBlendedTransparent(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
|
||||
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, props: Props) {
|
||||
const volumeRendering = scene.volumes.renderables.length > 0;
|
||||
const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
|
||||
const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
|
||||
const markingEnabled = MarkingPass.isEnabled(markingProps);
|
||||
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
|
||||
const antialiasingEnabled = AntialiasingPass.isEnabled(props.postprocessing);
|
||||
const markingEnabled = MarkingPass.isEnabled(props.marking);
|
||||
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
renderer.setViewport(x, y, width, height);
|
||||
renderer.update(camera);
|
||||
|
||||
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
|
||||
if (props.transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
|
||||
this.drawTarget.bind();
|
||||
renderer.clear(false);
|
||||
}
|
||||
|
||||
if (this.wboitEnabled) {
|
||||
this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
|
||||
this._renderWboit(renderer, camera, scene, props.transparentBackground, props.postprocessing);
|
||||
} else {
|
||||
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
|
||||
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, props.transparentBackground, props.postprocessing);
|
||||
}
|
||||
|
||||
if (postprocessingEnabled) {
|
||||
@@ -294,19 +307,22 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
if (markingEnabled) {
|
||||
const markingDepthTest = markingProps.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest) {
|
||||
this.marking.depthTarget.bind();
|
||||
renderer.clear(false);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
const markerAverage = scene.getMarkerAverage();
|
||||
if (markerAverage > 0) {
|
||||
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest && markerAverage !== 1) {
|
||||
this.marking.depthTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(props.marking);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(markingProps);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
if (helper.debug.isEnabled) {
|
||||
@@ -323,7 +339,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
if (antialiasingEnabled) {
|
||||
this.antialiasing.render(camera, toDrawingBuffer, postprocessingProps);
|
||||
this.antialiasing.render(camera, toDrawingBuffer, props.postprocessing);
|
||||
} else if (toDrawingBuffer) {
|
||||
this.drawTarget.bind();
|
||||
|
||||
@@ -338,16 +354,17 @@ export class DrawPass {
|
||||
this.webgl.gl.flush();
|
||||
}
|
||||
|
||||
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
|
||||
renderer.setTransparentBackground(transparentBackground);
|
||||
render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
|
||||
const { renderer, camera, scene, helper } = ctx;
|
||||
renderer.setTransparentBackground(props.transparentBackground);
|
||||
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
|
||||
renderer.setPixelRatio(this.webgl.pixelRatio);
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, props);
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, props);
|
||||
} else {
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, props);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,11 +83,12 @@ export class ImagePass {
|
||||
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height);
|
||||
this._camera.update();
|
||||
|
||||
const ctx = { renderer: this.renderer, camera: this._camera, scene: this.scene, helper: this.helper };
|
||||
if (MultiSamplePass.isEnabled(this.props.multiSample)) {
|
||||
this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
|
||||
this.multiSampleHelper.render(ctx, this.props, false);
|
||||
this._colorTarget = this.multiSamplePass.colorTarget;
|
||||
} else {
|
||||
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking);
|
||||
this.drawPass.render(ctx, this.props, false);
|
||||
this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Color } from '../../mol-util/color';
|
||||
import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
|
||||
|
||||
export const MarkingParams = {
|
||||
enabled: PD.Boolean(false),
|
||||
enabled: PD.Boolean(true),
|
||||
highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
|
||||
selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
|
||||
edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -59,6 +59,14 @@ type Props = {
|
||||
multiSample: MultiSampleProps
|
||||
postprocessing: PostprocessingProps
|
||||
marking: MarkingProps
|
||||
transparentBackground: boolean;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
renderer: Renderer;
|
||||
camera: Camera | StereoCamera;
|
||||
scene: Scene;
|
||||
helper: Helper;
|
||||
}
|
||||
|
||||
export class MultiSamplePass {
|
||||
@@ -97,12 +105,12 @@ export class MultiSamplePass {
|
||||
}
|
||||
}
|
||||
|
||||
render(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
|
||||
if (props.multiSample.mode === 'temporal') {
|
||||
return this.renderTemporalMultiSample(sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
render(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean, forceOn: boolean) {
|
||||
if (props.multiSample.mode === 'temporal' && !forceOn) {
|
||||
return this.renderTemporalMultiSample(sampleIndex, ctx, props, toDrawingBuffer);
|
||||
} else {
|
||||
this.renderMultiSample(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
return sampleIndex;
|
||||
this.renderMultiSample(ctx, toDrawingBuffer, props);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +122,8 @@ export class MultiSamplePass {
|
||||
}
|
||||
}
|
||||
|
||||
private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
|
||||
private renderMultiSample(ctx: RenderContext, toDrawingBuffer: boolean, props: Props) {
|
||||
const { camera } = ctx;
|
||||
const { compose, composeTarget, drawPass, webgl } = this;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
@@ -148,7 +157,15 @@ export class MultiSamplePass {
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
|
||||
if (i === 0) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
offset[0] / width,
|
||||
offset[1] / height
|
||||
);
|
||||
}
|
||||
drawPass.render(ctx, props, false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind();
|
||||
@@ -166,6 +183,8 @@ export class MultiSamplePass {
|
||||
compose.render();
|
||||
}
|
||||
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
@@ -181,7 +200,8 @@ export class MultiSamplePass {
|
||||
camera.update();
|
||||
}
|
||||
|
||||
private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
|
||||
private renderTemporalMultiSample(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
|
||||
const { camera } = ctx;
|
||||
const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
@@ -198,7 +218,7 @@ export class MultiSamplePass {
|
||||
const sampleWeight = 1.0 / offsetList.length;
|
||||
|
||||
if (sampleIndex === -1) {
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
|
||||
drawPass.render(ctx, props, false);
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
|
||||
compose.update();
|
||||
@@ -226,7 +246,15 @@ export class MultiSamplePass {
|
||||
camera.update();
|
||||
|
||||
// render scene
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
|
||||
if (sampleIndex === 0) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
offset[0] / width,
|
||||
offset[1] / height
|
||||
);
|
||||
}
|
||||
drawPass.render(ctx, props, false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind();
|
||||
@@ -248,6 +276,8 @@ export class MultiSamplePass {
|
||||
}
|
||||
}
|
||||
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
|
||||
this.bindOutputTarget(toDrawingBuffer);
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
@@ -281,23 +311,23 @@ const JitterVectors = [
|
||||
[0, 0]
|
||||
],
|
||||
[
|
||||
[4, 4], [-4, -4]
|
||||
[0, 0], [-4, -4]
|
||||
],
|
||||
[
|
||||
[-2, -6], [6, -2], [-6, 2], [2, 6]
|
||||
[0, 0], [6, -2], [-6, 2], [2, 6]
|
||||
],
|
||||
[
|
||||
[1, -3], [-1, 3], [5, 1], [-3, -5],
|
||||
[0, 0], [-1, 3], [5, 1], [-3, -5],
|
||||
[-5, 5], [-7, -1], [3, 7], [7, -7]
|
||||
],
|
||||
[
|
||||
[1, 1], [-1, -3], [-3, 2], [4, -1],
|
||||
[0, 0], [-1, -3], [-3, 2], [4, -1],
|
||||
[-5, -2], [2, 5], [5, 3], [3, -5],
|
||||
[-2, 6], [0, -7], [-4, -6], [-6, 4],
|
||||
[-8, 0], [7, -4], [6, 7], [-7, -8]
|
||||
],
|
||||
[
|
||||
[-4, -7], [-7, -5], [-3, -5], [-5, -4],
|
||||
[0, 0], [-7, -5], [-3, -5], [-5, -4],
|
||||
[-1, -4], [-2, -2], [-6, -1], [-4, 0],
|
||||
[-7, 1], [-1, 2], [-6, 3], [-3, 3],
|
||||
[-7, 6], [-3, 6], [-5, 7], [-1, 7],
|
||||
@@ -325,8 +355,8 @@ export class MultiSampleHelper {
|
||||
}
|
||||
|
||||
/** Return `true` while more samples are needed */
|
||||
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
|
||||
this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean, forceOn?: boolean) {
|
||||
this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, ctx, props, toDrawingBuffer, !!forceOn);
|
||||
return this.sampleIndex < 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { spiral2d } from '../../mol-math/misc';
|
||||
import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
|
||||
import { unpackRGBToInt, unpackRGBAToDepth } from '../../mol-util/number-packing';
|
||||
import { Camera, ICamera } from '../camera';
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
import { cameraUnproject } from '../camera/util';
|
||||
@@ -174,7 +174,7 @@ export class PickHelper {
|
||||
|
||||
private getId(x: number, y: number, buffer: Uint8Array) {
|
||||
const idx = this.getBufferIdx(x, y);
|
||||
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
|
||||
return unpackRGBToInt(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
|
||||
}
|
||||
|
||||
private render(camera: Camera | StereoCamera) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -199,6 +199,7 @@ const PostprocessingSchema = {
|
||||
uMaxPossibleViewZDiff: UniformSpec('f'),
|
||||
|
||||
dOcclusionEnable: DefineSpec('boolean'),
|
||||
uOcclusionOffset: UniformSpec('v2'),
|
||||
|
||||
dOutlineEnable: DefineSpec('boolean'),
|
||||
dOutlineScale: DefineSpec('number'),
|
||||
@@ -227,6 +228,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uMaxPossibleViewZDiff: ValueCell.create(0.5),
|
||||
|
||||
dOcclusionEnable: ValueCell.create(true),
|
||||
uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
|
||||
|
||||
dOutlineEnable: ValueCell.create(false),
|
||||
dOutlineScale: ValueCell.create(1),
|
||||
@@ -244,9 +246,10 @@ export const PostprocessingParams = {
|
||||
occlusion: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
|
||||
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
|
||||
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
|
||||
resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
@@ -281,6 +284,9 @@ export class PostprocessingPass {
|
||||
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
|
||||
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
|
||||
|
||||
private readonly downsampledDepthTarget: RenderTarget;
|
||||
private readonly downsampleDepthRenderable: CopyRenderable;
|
||||
|
||||
private readonly ssaoDepthTexture: Texture;
|
||||
private readonly ssaoDepthBlurProxyTexture: Texture;
|
||||
|
||||
@@ -290,24 +296,25 @@ export class PostprocessingPass {
|
||||
|
||||
private nSamples: number;
|
||||
private blurKernelSize: number;
|
||||
private downsampleFactor: number;
|
||||
|
||||
private readonly renderable: PostprocessingRenderable;
|
||||
|
||||
private ssaoScale: number;
|
||||
private calcSsaoScale() {
|
||||
// downscale ssao for high pixel-ratios
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio);
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
|
||||
const { colorTarget, depthTexture } = drawPass;
|
||||
const width = colorTarget.getWidth();
|
||||
const height = colorTarget.getHeight();
|
||||
|
||||
this.nSamples = 1;
|
||||
this.blurKernelSize = 1;
|
||||
this.downsampleFactor = 1;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
// needs to be linear for anti-aliasing pass
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
@@ -332,17 +339,20 @@ export class PostprocessingPass {
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
|
||||
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTexture);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTexture : this.downsampledDepthTarget.texture);
|
||||
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
|
||||
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
|
||||
@@ -359,11 +369,13 @@ export class PostprocessingPass {
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
@@ -434,6 +446,30 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
}
|
||||
|
||||
if (this.downsampleFactor !== props.occlusion.params.resolutionScale) {
|
||||
needsUpdateSsao = true;
|
||||
|
||||
this.downsampleFactor = props.occlusion.params.resolutionScale;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
const sw = Math.floor(w * this.ssaoScale);
|
||||
const sh = Math.floor(h * this.ssaoScale);
|
||||
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
if (this.ssaoScale === 1) {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTexture);
|
||||
} else {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
|
||||
}
|
||||
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
}
|
||||
}
|
||||
|
||||
if (props.outline.name === 'on') {
|
||||
@@ -494,6 +530,13 @@ export class PostprocessingPass {
|
||||
gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
private occlusionOffset: [x: number, y: number] = [0, 0];
|
||||
setOcclusionOffset(x: number, y: number) {
|
||||
this.occlusionOffset[0] = x;
|
||||
this.occlusionOffset[1] = y;
|
||||
ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y));
|
||||
}
|
||||
|
||||
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
|
||||
this.updateState(camera, transparentBackground, backgroundColor, props);
|
||||
|
||||
@@ -502,14 +545,13 @@ export class PostprocessingPass {
|
||||
this.outlinesRenderable.render();
|
||||
}
|
||||
|
||||
if (props.occlusion.name === 'on') {
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
const sx = Math.floor(x * this.ssaoScale);
|
||||
const sy = Math.floor(y * this.ssaoScale);
|
||||
const sw = Math.ceil(width * this.ssaoScale);
|
||||
const sh = Math.ceil(height * this.ssaoScale);
|
||||
this.webgl.gl.viewport(sx, sy, sw, sh);
|
||||
this.webgl.gl.scissor(sx, sy, sw, sh);
|
||||
// don't render occlusion if offset is given,
|
||||
// which will reuse the existing occlusion
|
||||
if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {
|
||||
if (this.ssaoScale < 1) {
|
||||
this.downsampledDepthTarget.bind();
|
||||
this.downsampleDepthRenderable.render();
|
||||
}
|
||||
|
||||
this.ssaoFramebuffer.bind();
|
||||
this.ssaoRenderable.render();
|
||||
@@ -519,9 +561,6 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoBlurSecondPassFramebuffer.bind();
|
||||
this.ssaoBlurSecondPassRenderable.render();
|
||||
|
||||
this.webgl.gl.viewport(x, y, width, height);
|
||||
this.webgl.gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
|
||||
16
src/mol-geo/geometry/_spec/marker.spec.ts
Normal file
16
src/mol-geo/geometry/_spec/marker.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { getMarkersAverage } from '../marker-data';
|
||||
|
||||
describe('marker-data', () => {
|
||||
it('getMarkersAverage', () => {
|
||||
expect(getMarkersAverage(new Uint8Array([0, 0, 0, 0]), 3)).toBe(0);
|
||||
expect(getMarkersAverage(new Uint8Array([0, 0, 1, 0]), 3)).toBe(1 / 3);
|
||||
expect(getMarkersAverage(new Uint8Array([0, 0, 0, 0]), 4)).toBe(0);
|
||||
expect(getMarkersAverage(new Uint8Array([0, 0, 1, 0]), 4)).toBe(1 / 4);
|
||||
});
|
||||
});
|
||||
@@ -15,7 +15,7 @@ import { LocationColor, ColorTheme } from '../../mol-theme/color';
|
||||
import { Geometry } from './geometry';
|
||||
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
|
||||
|
||||
export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance'
|
||||
export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance' | 'direct'
|
||||
|
||||
export type ColorData = {
|
||||
uColor: ValueCell<Vec3>,
|
||||
@@ -50,6 +50,7 @@ function _createColors(locationIt: LocationIterator, positionIt: LocationIterato
|
||||
case 'vertexInstance': return createVertexInstanceColor(positionIt, colorTheme.color, colorData);
|
||||
case 'volume': return createGridColor((colorTheme as any).grid, 'volume', colorData);
|
||||
case 'volumeInstance': return createGridColor((colorTheme as any).grid, 'volumeInstance', colorData);
|
||||
case 'direct': return createDirectColor(colorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,3 +238,25 @@ export function createGridColor(grid: ColorVolume, type: ColorType, colorData?:
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/** Creates direct color */
|
||||
function createDirectColor(colorData?: ColorData): ColorData {
|
||||
if (colorData) {
|
||||
ValueCell.updateIfChanged(colorData.dColorType, 'direct');
|
||||
return colorData;
|
||||
} else {
|
||||
return {
|
||||
uColor: ValueCell.create(Vec3()),
|
||||
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
|
||||
tColorGrid: ValueCell.create(createNullTexture()),
|
||||
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
|
||||
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
|
||||
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
|
||||
uColorGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
|
||||
dColorType: ValueCell.create('direct'),
|
||||
dUsePalette: ValueCell.create(false),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -213,6 +213,8 @@ export namespace Cylinders {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('cylinders'),
|
||||
|
||||
aMapping: cylinders.mappingBuffer,
|
||||
aGroup: cylinders.groupBuffer,
|
||||
aStart: cylinders.startBuffer,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -26,8 +26,7 @@ import { TransformData } from '../transform-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
import { createEmptyClipping } from '../clipping-data';
|
||||
import { Grid, Volume } from '../../../mol-model/volume';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { Grid } from '../../../mol-model/volume';
|
||||
import { createEmptySubstance } from '../substance-data';
|
||||
|
||||
const VolumeBox = Box();
|
||||
@@ -48,6 +47,7 @@ export interface DirectVolume {
|
||||
readonly unitToCartn: ValueCell<Mat4>
|
||||
readonly cartnToUnit: ValueCell<Mat4>
|
||||
readonly packedGroup: ValueCell<boolean>
|
||||
readonly axisOrder: ValueCell<Vec3>
|
||||
|
||||
/** Bounding sphere of the volume */
|
||||
readonly boundingSphere: Sphere3D
|
||||
@@ -56,10 +56,10 @@ export interface DirectVolume {
|
||||
}
|
||||
|
||||
export namespace DirectVolume {
|
||||
export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume?: DirectVolume): DirectVolume {
|
||||
export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume?: DirectVolume): DirectVolume {
|
||||
return directVolume ?
|
||||
update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume) :
|
||||
fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup);
|
||||
update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume) :
|
||||
fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder);
|
||||
}
|
||||
|
||||
function hashCode(directVolume: DirectVolume) {
|
||||
@@ -70,7 +70,7 @@ export namespace DirectVolume {
|
||||
]);
|
||||
}
|
||||
|
||||
function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean): DirectVolume {
|
||||
function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3): DirectVolume {
|
||||
const boundingSphere = Sphere3D();
|
||||
let currentHash = -1;
|
||||
|
||||
@@ -101,6 +101,7 @@ export namespace DirectVolume {
|
||||
return boundingSphere;
|
||||
},
|
||||
packedGroup: ValueCell.create(packedGroup),
|
||||
axisOrder: ValueCell.create(axisOrder),
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere);
|
||||
currentHash = hashCode(directVolume);
|
||||
@@ -109,7 +110,7 @@ export namespace DirectVolume {
|
||||
return directVolume;
|
||||
}
|
||||
|
||||
function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume: DirectVolume): DirectVolume {
|
||||
function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume: DirectVolume): DirectVolume {
|
||||
const width = texture.getWidth();
|
||||
const height = texture.getHeight();
|
||||
const depth = texture.getDepth();
|
||||
@@ -126,6 +127,7 @@ export namespace DirectVolume {
|
||||
ValueCell.update(directVolume.unitToCartn, unitToCartn);
|
||||
ValueCell.update(directVolume.cartnToUnit, Mat4.invert(Mat4(), unitToCartn));
|
||||
ValueCell.updateIfChanged(directVolume.packedGroup, packedGroup);
|
||||
ValueCell.updateIfChanged(directVolume.axisOrder, Vec3.fromArray(directVolume.axisOrder.ref.value, axisOrder, 0));
|
||||
return directVolume;
|
||||
}
|
||||
|
||||
@@ -138,47 +140,19 @@ export namespace DirectVolume {
|
||||
const texture = createNullTexture();
|
||||
const stats = Grid.One.stats;
|
||||
const packedGroup = false;
|
||||
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume);
|
||||
}
|
||||
|
||||
export function createRenderModeParam(stats?: Grid['stats']) {
|
||||
const isoValueParam = stats
|
||||
? Volume.createIsoValueParam(Volume.IsoValue.relative(2), stats)
|
||||
: Volume.IsoValueParam;
|
||||
|
||||
return PD.MappedStatic('volume', {
|
||||
isosurface: PD.Group({
|
||||
isoValue: isoValueParam,
|
||||
singleLayer: PD.Boolean(false, { isEssential: true }),
|
||||
}, { isFlat: true }),
|
||||
volume: PD.Group({
|
||||
controlPoints: PD.LineGraph([
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
|
||||
]),
|
||||
list: PD.ColorList({
|
||||
kind: 'interpolate',
|
||||
colors: [
|
||||
[ColorNames.white, 0],
|
||||
[ColorNames.red, 0.25],
|
||||
[ColorNames.white, 0.5],
|
||||
[ColorNames.blue, 0.75],
|
||||
[ColorNames.white, 1]
|
||||
]
|
||||
}, { offsets: true }),
|
||||
}, { isFlat: true })
|
||||
}, { isEssential: true });
|
||||
const axisOrder = Vec3.create(0, 1, 2);
|
||||
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume);
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
renderMode: createRenderModeParam(),
|
||||
stepsPerCell: PD.Numeric(5, { min: 1, max: 20, step: 1 }),
|
||||
controlPoints: PD.LineGraph([
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
|
||||
], { isEssential: true }),
|
||||
stepsPerCell: PD.Numeric(3, { min: 1, max: 10, step: 1 }),
|
||||
jumpLength: PD.Numeric(0, { min: 0, max: 20, step: 0.1 }),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
@@ -217,13 +191,6 @@ export namespace DirectVolume {
|
||||
return LocationIterator(groupCount, instanceCount, 1, getLocation);
|
||||
}
|
||||
|
||||
function getNormalizedIsoValue(out: Vec2, isoValue: Volume.IsoValue, stats: Vec4) {
|
||||
const [min, max, mean, sigma] = stats;
|
||||
const value = Volume.IsoValue.toAbsolute(isoValue, { min, max, mean, sigma }).absoluteValue;
|
||||
Vec2.set(out, (value - min) / (max - min), (0 - min) / (max - min));
|
||||
return out;
|
||||
}
|
||||
|
||||
function getMaxSteps(gridDim: Vec3, stepsPerCell: number) {
|
||||
return Math.ceil(Vec3.magnitude(gridDim) * stepsPerCell);
|
||||
}
|
||||
@@ -256,18 +223,12 @@ export namespace DirectVolume {
|
||||
const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
const controlPoints = props.renderMode.name === 'volume' ? getControlPointsFromVec2Array(props.renderMode.params.controlPoints) : [];
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.renderMode.name === 'volume' ? props.renderMode.params.list.colors : []);
|
||||
|
||||
const isoValue = props.renderMode.name === 'isosurface'
|
||||
? props.renderMode.params.isoValue
|
||||
: Volume.IsoValue.relative(2);
|
||||
|
||||
const singleLayer = props.renderMode.name === 'isosurface'
|
||||
? props.renderMode.params.singleLayer
|
||||
: false;
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
|
||||
const transferTex = createTransferFunctionTexture(controlPoints);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('directVolume'),
|
||||
|
||||
...color,
|
||||
...marker,
|
||||
...overpaint,
|
||||
@@ -283,7 +244,6 @@ export namespace DirectVolume {
|
||||
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
|
||||
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
|
||||
|
||||
uIsoValue: ValueCell.create(getNormalizedIsoValue(Vec2(), isoValue, directVolume.gridStats.ref.value)),
|
||||
uBboxMin: bboxMin,
|
||||
uBboxMax: bboxMax,
|
||||
uBboxSize: bboxSize,
|
||||
@@ -292,7 +252,6 @@ export namespace DirectVolume {
|
||||
uJumpLength: ValueCell.create(props.jumpLength),
|
||||
uTransform: gridTransform,
|
||||
uGridDim: gridDimension,
|
||||
dRenderMode: ValueCell.create(props.renderMode.name),
|
||||
tTransferTex: transferTex,
|
||||
uTransferScale: ValueCell.create(getTransferScale(props.stepsPerCell)),
|
||||
|
||||
@@ -305,11 +264,8 @@ export namespace DirectVolume {
|
||||
uCartnToUnit: directVolume.cartnToUnit,
|
||||
uUnitToCartn: directVolume.unitToCartn,
|
||||
dPackedGroup: directVolume.packedGroup,
|
||||
dSingleLayer: ValueCell.create(singleLayer),
|
||||
dAxisOrder: ValueCell.create(directVolume.axisOrder.ref.value.join('')),
|
||||
|
||||
uDoubleSided: ValueCell.create(props.doubleSided),
|
||||
dFlatShaded: ValueCell.create(props.flatShaded),
|
||||
dFlipSided: ValueCell.create(props.flipSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
};
|
||||
@@ -323,20 +279,11 @@ export namespace DirectVolume {
|
||||
|
||||
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
|
||||
BaseGeometry.updateValues(values, props);
|
||||
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
|
||||
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
|
||||
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
ValueCell.updateIfChanged(values.dRenderMode, props.renderMode.name);
|
||||
|
||||
if (props.renderMode.name === 'isosurface') {
|
||||
ValueCell.updateIfChanged(values.uIsoValue, getNormalizedIsoValue(values.uIsoValue.ref.value, props.renderMode.params.isoValue, values.uGridStats.ref.value));
|
||||
ValueCell.updateIfChanged(values.dSingleLayer, props.renderMode.params.singleLayer);
|
||||
} else if (props.renderMode.name === 'volume') {
|
||||
const controlPoints = getControlPointsFromVec2Array(props.renderMode.params.controlPoints);
|
||||
createTransferFunctionTexture(controlPoints, props.renderMode.params.list.colors, values.tTransferTex);
|
||||
}
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
|
||||
createTransferFunctionTexture(controlPoints, values.tTransferTex);
|
||||
|
||||
ValueCell.updateIfChanged(values.uMaxSteps, getMaxSteps(values.uGridDim.ref.value, props.stepsPerCell));
|
||||
ValueCell.updateIfChanged(values.uStepScale, getStepScale(values.uCellDim.ref.value, props.stepsPerCell));
|
||||
@@ -360,14 +307,14 @@ export namespace DirectVolume {
|
||||
function createRenderableState(props: PD.Values<Params>): RenderableState {
|
||||
const state = BaseGeometry.createRenderableState(props);
|
||||
state.opaque = false;
|
||||
state.writeDepth = props.renderMode.name === 'isosurface';
|
||||
state.writeDepth = false;
|
||||
return state;
|
||||
}
|
||||
|
||||
function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
|
||||
BaseGeometry.updateRenderableState(state, props);
|
||||
state.opaque = false;
|
||||
state.writeDepth = props.renderMode.name === 'isosurface';
|
||||
state.writeDepth = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { TextureImage } from '../../../mol-gl/renderable/util';
|
||||
import { spline } from '../../../mol-math/interpolate';
|
||||
import { ColorScale } from '../../../mol-util/color';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { ColorListName } from '../../../mol-util/color/lists';
|
||||
import { ColorListEntry } from '../../../mol-util/color/color';
|
||||
|
||||
export interface ControlPoint { x: number, alpha: number }
|
||||
|
||||
@@ -25,7 +22,7 @@ export function getControlPointsFromVec2Array(array: Vec2[]): ControlPoint[] {
|
||||
return array.map(v => ({ x: v[0], alpha: v[1] }));
|
||||
}
|
||||
|
||||
export function createTransferFunctionTexture(controlPoints: ControlPoint[], listOrName: ColorListEntry[] | ColorListName, texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> {
|
||||
export function createTransferFunctionTexture(controlPoints: ControlPoint[], texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> {
|
||||
const cp = [
|
||||
{ x: 0, alpha: 0 },
|
||||
{ x: 0, alpha: 0 },
|
||||
@@ -33,10 +30,9 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], lis
|
||||
{ x: 1, alpha: 0 },
|
||||
{ x: 1, alpha: 0 },
|
||||
];
|
||||
const scale = ColorScale.create({ domain: [0, 1], listOrName });
|
||||
|
||||
const n = 256;
|
||||
const array = texture ? texture.ref.value.array : new Uint8Array(n * 4);
|
||||
const array = texture ? texture.ref.value.array : new Uint8Array(n);
|
||||
|
||||
let k = 0;
|
||||
let x1: number, x2: number;
|
||||
@@ -55,8 +51,7 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], lis
|
||||
const jl = Math.round((x2 - x1) * n);
|
||||
for (let j = 0; j < jl; ++j) {
|
||||
const t = j / jl;
|
||||
array[k * 4 + 3] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255);
|
||||
scale.colorToArray(k / 255, array, k * 4);
|
||||
array[k] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255);
|
||||
++k;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,8 @@ namespace Image {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('image'),
|
||||
|
||||
...color,
|
||||
...marker,
|
||||
...overpaint,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -165,7 +165,7 @@ export namespace Lines {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
sizeFactor: PD.Numeric(2, { min: 0, max: 10, step: 0.1 }),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
@@ -220,6 +220,8 @@ export namespace Lines {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('lines'),
|
||||
|
||||
aMapping: lines.mappingBuffer,
|
||||
aGroup: lines.groupBuffer,
|
||||
aStart: lines.startBuffer,
|
||||
|
||||
@@ -47,12 +47,19 @@ export function getMarkersAverage(array: Uint8Array, count: number): number {
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
if (viewEnd < 0) {
|
||||
// avoid edge cases with small arrays
|
||||
for (let i = 0; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
@@ -677,6 +677,8 @@ export namespace Mesh {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('mesh'),
|
||||
|
||||
aPosition: mesh.vertexBuffer,
|
||||
aNormal: mesh.normalBuffer,
|
||||
aGroup: mesh.groupBuffer,
|
||||
|
||||
@@ -182,6 +182,8 @@ export namespace Points {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('points'),
|
||||
|
||||
aPosition: points.centerBuffer,
|
||||
aGroup: points.groupBuffer,
|
||||
boundingSphere: ValueCell.create(boundingSphere),
|
||||
|
||||
@@ -11,7 +11,7 @@ import { LocationIterator } from '../util/location-iterator';
|
||||
import { Location, NullLocation } from '../../mol-model/location';
|
||||
import { SizeTheme } from '../../mol-theme/size';
|
||||
import { Geometry } from './geometry';
|
||||
import { decodeFloatRGB, encodeFloatRGBtoArray } from '../../mol-util/float-packing';
|
||||
import { unpackRGBToInt, packIntToRGBArray } from '../../mol-util/number-packing';
|
||||
|
||||
export type SizeType = 'uniform' | 'instance' | 'group' | 'groupInstance'
|
||||
|
||||
@@ -44,7 +44,7 @@ export function getMaxSize(sizeData: SizeData): number {
|
||||
let maxSize = 0;
|
||||
const array = sizeData.tSize.ref.value.array;
|
||||
for (let i = 0, il = array.length; i < il; i += 3) {
|
||||
const value = decodeFloatRGB(array[i], array[i + 1], array[i + 2]);
|
||||
const value = unpackRGBToInt(array[i], array[i + 1], array[i + 2]);
|
||||
if (maxSize < value) maxSize = value;
|
||||
}
|
||||
return maxSize / sizeDataFactor;
|
||||
@@ -103,7 +103,7 @@ export function createInstanceSize(locationIt: LocationIterator, sizeFn: Locatio
|
||||
locationIt.reset();
|
||||
while (locationIt.hasNext && !locationIt.isNextNewInstance) {
|
||||
const v = locationIt.move();
|
||||
encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.instanceIndex * 3);
|
||||
packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.instanceIndex * 3);
|
||||
locationIt.skipInstance();
|
||||
}
|
||||
return createTextureSize(sizes, 'instance', sizeData);
|
||||
@@ -116,7 +116,7 @@ export function createGroupSize(locationIt: LocationIterator, sizeFn: LocationSi
|
||||
locationIt.reset();
|
||||
while (locationIt.hasNext && !locationIt.isNextNewInstance) {
|
||||
const v = locationIt.move();
|
||||
encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.groupIndex * 3);
|
||||
packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.groupIndex * 3);
|
||||
}
|
||||
return createTextureSize(sizes, 'group', sizeData);
|
||||
}
|
||||
@@ -129,7 +129,7 @@ export function createGroupInstanceSize(locationIt: LocationIterator, sizeFn: Lo
|
||||
locationIt.reset();
|
||||
while (locationIt.hasNext) {
|
||||
const v = locationIt.move();
|
||||
encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.index * 3);
|
||||
packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.index * 3);
|
||||
}
|
||||
return createTextureSize(sizes, 'groupInstance', sizeData);
|
||||
}
|
||||
@@ -183,6 +183,8 @@ export namespace Spheres {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('spheres'),
|
||||
|
||||
aPosition: spheres.centerBuffer,
|
||||
aMapping: spheres.mappingBuffer,
|
||||
aGroup: spheres.groupBuffer,
|
||||
|
||||
@@ -224,6 +224,8 @@ export namespace Text {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('text'),
|
||||
|
||||
aPosition: text.centerBuffer,
|
||||
aMapping: text.mappingBuffer,
|
||||
aDepth: text.depthBuffer,
|
||||
|
||||
@@ -147,6 +147,8 @@ export namespace TextureMesh {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('textureMesh'),
|
||||
|
||||
uGeoTexDim: textureMesh.geoTextureDim,
|
||||
tPosition: textureMesh.vertexTexture,
|
||||
tGroup: textureMesh.groupTexture,
|
||||
@@ -172,7 +174,6 @@ export namespace TextureMesh {
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
dGeoTexture: ValueCell.create(true),
|
||||
|
||||
meta: ValueCell.create(textureMesh.meta),
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('renderer', () => {
|
||||
expect(ctx.gl.drawingBufferHeight).toBe(32);
|
||||
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(0);
|
||||
@@ -52,7 +52,7 @@ describe('renderer', () => {
|
||||
scene.add(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(8);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(9);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
@@ -60,7 +60,7 @@ describe('renderer', () => {
|
||||
scene.remove(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Values, TextureSpec } from '../../renderable/schema';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { ShaderCode } from '../../../mol-gl/shader-code';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { decodeFloatRGB } from '../../../mol-util/float-packing';
|
||||
import { unpackRGBToInt } from '../../../mol-util/number-packing';
|
||||
import { QuadSchema, QuadValues } from '../util';
|
||||
import { quad_vert } from '../../../mol-gl/shader/quad.vert';
|
||||
import { sum_frag } from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
|
||||
@@ -96,5 +96,5 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
|
||||
|
||||
return isWebGL2(gl)
|
||||
? sumInts[0]
|
||||
: decodeFloatRGB(sumBytes[0], sumBytes[1], sumBytes[2]);
|
||||
: unpackRGBToInt(sumBytes[0], sumBytes[1], sumBytes[2]);
|
||||
}
|
||||
@@ -39,13 +39,14 @@ const IsosurfaceSchema = {
|
||||
uGridTransform: UniformSpec('m4'),
|
||||
uScale: UniformSpec('v2'),
|
||||
|
||||
dPackedGroup: DefineSpec('boolean')
|
||||
dPackedGroup: DefineSpec('boolean'),
|
||||
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
|
||||
};
|
||||
type IsosurfaceValues = Values<typeof IsosurfaceSchema>
|
||||
|
||||
const IsosurfaceName = 'isosurface';
|
||||
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3): ComputeRenderable<IsosurfaceValues> {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
|
||||
|
||||
@@ -65,15 +66,16 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
ValueCell.update(v.uScale, scale);
|
||||
|
||||
ValueCell.update(v.dPackedGroup, packedGroup);
|
||||
ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join(''));
|
||||
|
||||
ctx.namedComputeRenderables[IsosurfaceName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
|
||||
}
|
||||
return ctx.namedComputeRenderables[IsosurfaceName];
|
||||
}
|
||||
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean) {
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3) {
|
||||
// console.log('uSize', Math.pow(2, levels))
|
||||
const values: IsosurfaceValues = {
|
||||
...QuadValues,
|
||||
@@ -94,7 +96,8 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
|
||||
uGridTransform: ValueCell.create(transform),
|
||||
uScale: ValueCell.create(scale),
|
||||
|
||||
dPackedGroup: ValueCell.create(packedGroup)
|
||||
dPackedGroup: ValueCell.create(packedGroup),
|
||||
dAxisOrder: ValueCell.create(axisOrder.join('')),
|
||||
};
|
||||
|
||||
const schema = { ...IsosurfaceSchema };
|
||||
@@ -115,7 +118,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
const { drawBuffers } = ctx.extensions;
|
||||
if (!drawBuffers) throw new Error('need WebGL draw buffers');
|
||||
|
||||
@@ -173,7 +176,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
groupTexture.attachFramebuffer(framebuffer, 1);
|
||||
normalTexture.attachFramebuffer(framebuffer, 2);
|
||||
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
|
||||
framebuffer.bind();
|
||||
@@ -204,7 +207,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
*
|
||||
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
|
||||
*/
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
@@ -216,7 +219,7 @@ export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDi
|
||||
// console.timeEnd('createHistogramPyramid');
|
||||
|
||||
// console.time('createIsosurfaceBuffers');
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, vertexTexture, groupTexture, normalTexture);
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, vertexTexture, groupTexture, normalTexture);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
|
||||
|
||||
@@ -17,12 +17,6 @@ export const DirectVolumeSchema = {
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
uColor: UniformSpec('v3'),
|
||||
uColorTexDim: UniformSpec('v2'),
|
||||
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
|
||||
|
||||
uIsoValue: UniformSpec('v2'),
|
||||
uBboxMin: UniformSpec('v3'),
|
||||
uBboxMax: UniformSpec('v3'),
|
||||
uBboxSize: UniformSpec('v3'),
|
||||
@@ -31,9 +25,7 @@ export const DirectVolumeSchema = {
|
||||
uJumpLength: UniformSpec('f'),
|
||||
uTransform: UniformSpec('m4'),
|
||||
uGridDim: UniformSpec('v3'),
|
||||
dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
|
||||
dSingleLayer: DefineSpec('boolean'),
|
||||
tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'),
|
||||
tTransferTex: TextureSpec('image-uint8', 'alpha', 'ubyte', 'linear'),
|
||||
uTransferScale: UniformSpec('f'),
|
||||
|
||||
dGridTexType: DefineSpec('string', ['2d', '3d']),
|
||||
@@ -45,10 +37,8 @@ export const DirectVolumeSchema = {
|
||||
uCartnToUnit: UniformSpec('m4'),
|
||||
uUnitToCartn: UniformSpec('m4'),
|
||||
dPackedGroup: DefineSpec('boolean'),
|
||||
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
|
||||
|
||||
uDoubleSided: UniformSpec('b'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dFlatShaded: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
};
|
||||
|
||||
@@ -182,7 +182,7 @@ export const ColorSchema = {
|
||||
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
tColorGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance', 'volume', 'volumeInstance']),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance', 'volume', 'volumeInstance', 'direct']),
|
||||
dUsePalette: DefineSpec('boolean'),
|
||||
} as const;
|
||||
export type ColorSchema = typeof ColorSchema
|
||||
@@ -258,6 +258,8 @@ export type ClippingSchema = typeof ClippingSchema
|
||||
export type ClippingValues = Values<ClippingSchema>
|
||||
|
||||
export const BaseSchema = {
|
||||
dGeometryType: DefineSpec('string', ['cylinders', 'directVolume', 'image', 'lines', 'mesh', 'points', 'spheres', 'text', 'textureMesh']),
|
||||
|
||||
...ColorSchema,
|
||||
...MarkerSchema,
|
||||
...OverpaintSchema,
|
||||
|
||||
@@ -23,7 +23,6 @@ export const TextureMeshSchema = {
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dGeoTexture: DefineSpec('boolean'),
|
||||
uBumpFrequency: UniformSpec('f'),
|
||||
uBumpAmplitude: UniformSpec('f'),
|
||||
meta: ValueSpec('unknown')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -16,7 +16,7 @@ import { GlobalUniformValues } from './renderable/schema';
|
||||
import { GraphicsRenderVariant } from './webgl/render-item';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { degToRad } from '../mol-math/misc';
|
||||
import { createNullTexture, Texture, Textures } from './webgl/texture';
|
||||
import { Texture, Textures } from './webgl/texture';
|
||||
import { arrayMapUpsert } from '../mol-util/array';
|
||||
import { clamp } from '../mol-math/interpolate';
|
||||
|
||||
@@ -53,7 +53,7 @@ interface Renderer {
|
||||
readonly stats: RendererStats
|
||||
readonly props: Readonly<RendererProps>
|
||||
|
||||
clear: (toBackgroundColor: boolean) => void
|
||||
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => void
|
||||
clearDepth: () => void
|
||||
update: (camera: ICamera) => void
|
||||
|
||||
@@ -89,8 +89,8 @@ export const RendererParams = {
|
||||
|
||||
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
|
||||
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
|
||||
highlightStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
|
||||
selectStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
|
||||
highlightStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
|
||||
selectStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
|
||||
markerPriority: PD.Select(1, [[1, 'Highlight'], [2, 'Select']]),
|
||||
|
||||
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
|
||||
@@ -136,7 +136,7 @@ function getLight(props: RendererProps['light'], light?: Light): Light {
|
||||
|
||||
namespace Renderer {
|
||||
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
|
||||
const { gl, state, stats, extensions: { fragDepth } } = ctx;
|
||||
const { gl, state, stats } = ctx;
|
||||
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
|
||||
const light = getLight(p.light);
|
||||
|
||||
@@ -146,9 +146,9 @@ namespace Renderer {
|
||||
|
||||
let transparentBackground = false;
|
||||
|
||||
const nullDepthTexture = createNullTexture(gl);
|
||||
const emptyDepthTexture = ctx.resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
const sharedTexturesList: Textures = [
|
||||
['tDepth', nullDepthTexture]
|
||||
['tDepth', emptyDepthTexture]
|
||||
];
|
||||
|
||||
const view = Mat4();
|
||||
@@ -245,9 +245,9 @@ namespace Renderer {
|
||||
globalUniformsNeedUpdate = false;
|
||||
}
|
||||
|
||||
if (r.values.dRenderMode) { // indicates direct-volume
|
||||
if ((variant === 'pick' || variant === 'depth') && r.values.dRenderMode.ref.value === 'volume') {
|
||||
return; // no picking/depth in volume mode
|
||||
if (r.values.dGeometryType.ref.value === 'directVolume') {
|
||||
if (variant !== 'colorWboit' && variant !== 'colorBlended') {
|
||||
return; // only color supported
|
||||
}
|
||||
|
||||
// culling done in fragment shader
|
||||
@@ -256,14 +256,8 @@ namespace Renderer {
|
||||
|
||||
if (variant === 'colorBlended') {
|
||||
// depth test done manually in shader against `depthTexture`
|
||||
// still need to enable when fragDepth can be used to write depth
|
||||
if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) {
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
} else {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(r.values.uAlpha.ref.value === 1.0);
|
||||
}
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
}
|
||||
} else {
|
||||
if (r.values.uDoubleSided) {
|
||||
@@ -315,7 +309,7 @@ namespace Renderer {
|
||||
};
|
||||
|
||||
const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => {
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
|
||||
|
||||
ValueCell.update(globalUniforms.uModel, group.view);
|
||||
ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view));
|
||||
@@ -506,7 +500,7 @@ namespace Renderer {
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
|
||||
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
|
||||
renderObject(r, 'colorWboit');
|
||||
}
|
||||
}
|
||||
@@ -522,20 +516,20 @@ namespace Renderer {
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
renderObject(r, 'colorWboit');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
clear: (toBackgroundColor: boolean) => {
|
||||
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.colorMask(true, true, true, true);
|
||||
state.depthMask(true);
|
||||
|
||||
if (transparentBackground) {
|
||||
if (transparentBackground && !ignoreTransparentBackground) {
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
} else if (toBackgroundColor) {
|
||||
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -79,6 +79,7 @@ interface Scene extends Object3D {
|
||||
has: (o: GraphicsRenderObject) => boolean
|
||||
clear: () => void
|
||||
forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
|
||||
getMarkerAverage: () => number
|
||||
}
|
||||
|
||||
namespace Scene {
|
||||
@@ -243,7 +244,18 @@ namespace Scene {
|
||||
visibleHash = computeVisibleHash();
|
||||
}
|
||||
return boundingSphereVisible;
|
||||
}
|
||||
},
|
||||
getMarkerAverage() {
|
||||
if (primitives.length === 0 && volumes.length === 0) return 0;
|
||||
let markerAverage = 0;
|
||||
for (let i = 0, il = primitives.length; i < il; ++i) {
|
||||
markerAverage += primitives[i].values.markerAverage.ref.value;
|
||||
}
|
||||
for (let i = 0, il = volumes.length; i < il; ++i) {
|
||||
markerAverage += volumes[i].values.markerAverage.ref.value;
|
||||
}
|
||||
return markerAverage / (primitives.length + volumes.length);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { drawBuf
|
||||
|
||||
import { directVolume_vert } from './shader/direct-volume.vert';
|
||||
import { directVolume_frag } from './shader/direct-volume.frag';
|
||||
export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_vert, directVolume_frag, { fragDepth: 'optional', drawBuffers: 'optional' });
|
||||
export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_vert, directVolume_frag, { fragDepth: 'optional', drawBuffers: 'optional' }, {}, ignoreDefine);
|
||||
|
||||
import { image_vert } from './shader/image.vert';
|
||||
import { image_frag } from './shader/image.frag';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
@@ -8,61 +8,65 @@
|
||||
*/
|
||||
|
||||
export const apply_light_color = `
|
||||
#ifdef bumpEnabled
|
||||
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) {
|
||||
vec3 bumpNormal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency);
|
||||
#ifdef enabledFragDepth
|
||||
if (!isNaN(bumpNormal.x) && !isNaN(bumpNormal.y) && !isNaN(bumpNormal.z)) {
|
||||
normal = bumpNormal;
|
||||
}
|
||||
#else
|
||||
normal = bumpNormal;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 color = material;
|
||||
|
||||
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
|
||||
|
||||
PhysicalMaterial physicalMaterial;
|
||||
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
|
||||
#ifdef enabledFragDepth
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525), 1.0);
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
vec3 dxy = max(abs(dFdx(normal)), abs(dFdy(normal)));
|
||||
float geometryRoughness = max(max(dxy.x, dxy.y), dxy.z);
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525) + geometryRoughness, 1.0);
|
||||
#ifdef bumpEnabled
|
||||
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) {
|
||||
vec3 bumpNormal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency);
|
||||
#ifdef enabledFragDepth
|
||||
if (!isNaN(bumpNormal.x) && !isNaN(bumpNormal.y) && !isNaN(bumpNormal.z)) {
|
||||
normal = bumpNormal;
|
||||
}
|
||||
#else
|
||||
normal = bumpNormal;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 color = material;
|
||||
|
||||
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
|
||||
|
||||
PhysicalMaterial physicalMaterial;
|
||||
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
|
||||
#ifdef enabledFragDepth
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525), 1.0);
|
||||
#else
|
||||
vec3 dxy = max(abs(dFdx(normal)), abs(dFdy(normal)));
|
||||
float geometryRoughness = max(max(dxy.x, dxy.y), dxy.z);
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525) + geometryRoughness, 1.0);
|
||||
#endif
|
||||
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
|
||||
physicalMaterial.specularF90 = 1.0;
|
||||
|
||||
GeometricContext geometry;
|
||||
geometry.position = -vViewPosition;
|
||||
geometry.normal = normal;
|
||||
geometry.viewDir = normalize(vViewPosition);
|
||||
|
||||
IncidentLight directLight;
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
|
||||
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
// indirect specular only metals
|
||||
vec3 radiance = uAmbientColor * metalness;
|
||||
vec3 iblIrradiance = uAmbientColor * metalness;
|
||||
vec3 clearcoatRadiance = vec3(0.0);
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
#endif
|
||||
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
|
||||
physicalMaterial.specularF90 = 1.0;
|
||||
|
||||
GeometricContext geometry;
|
||||
geometry.position = -vViewPosition;
|
||||
geometry.normal = normal;
|
||||
geometry.viewDir = normalize(vViewPosition);
|
||||
|
||||
IncidentLight directLight;
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
|
||||
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
// indirect specular only metals
|
||||
vec3 radiance = uAmbientColor * metalness;
|
||||
vec3 iblIrradiance = uAmbientColor * metalness;
|
||||
vec3 clearcoatRadiance = vec3(0.0);
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
|
||||
#ifdef dXrayShaded
|
||||
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
|
||||
|
||||
@@ -57,11 +57,11 @@ export const assign_color_varying = `
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
if (uPickType == 1) {
|
||||
vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
vColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
|
||||
} else if (uPickType == 2) {
|
||||
vColor = vec4(encodeFloatRGB(aInstance), 1.0);
|
||||
vColor = vec4(packIntToRGB(aInstance), 1.0);
|
||||
} else {
|
||||
vColor = vec4(encodeFloatRGB(group), 1.0);
|
||||
vColor = vec4(packIntToRGB(group), 1.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const assign_group = `
|
||||
#ifdef dGeoTexture
|
||||
float group = decodeFloatRGB(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
|
||||
#ifdef dGeometryType_textureMesh
|
||||
float group = unpackRGBToInt(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
|
||||
#else
|
||||
float group = aGroup;
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const assign_position = `
|
||||
mat4 model = uModel * aTransform;
|
||||
mat4 modelView = uView * model;
|
||||
#ifdef dGeoTexture
|
||||
#ifdef dGeometryType_textureMesh
|
||||
vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 position = aPosition;
|
||||
|
||||
@@ -4,11 +4,11 @@ export const assign_size = `
|
||||
#elif defined(dSizeType_attribute)
|
||||
float size = aSize;
|
||||
#elif defined(dSizeType_instance)
|
||||
float size = decodeFloatRGB(readFromTexture(tSize, aInstance, uSizeTexDim).rgb);
|
||||
float size = unpackRGBToInt(readFromTexture(tSize, aInstance, uSizeTexDim).rgb);
|
||||
#elif defined(dSizeType_group)
|
||||
float size = decodeFloatRGB(readFromTexture(tSize, group, uSizeTexDim).rgb);
|
||||
float size = unpackRGBToInt(readFromTexture(tSize, group, uSizeTexDim).rgb);
|
||||
#elif defined(dSizeType_groupInstance)
|
||||
float size = decodeFloatRGB(readFromTexture(tSize, aInstance * float(uGroupCount) + group, uSizeTexDim).rgb);
|
||||
float size = unpackRGBToInt(readFromTexture(tSize, aInstance * float(uGroupCount) + group, uSizeTexDim).rgb);
|
||||
#endif
|
||||
|
||||
#if defined(dSizeType_instance) || defined(dSizeType_group) || defined(dSizeType_groupInstance)
|
||||
|
||||
@@ -25,6 +25,10 @@ export const common = `
|
||||
|
||||
#define saturate(a) clamp(a, 0.0, 1.0)
|
||||
|
||||
#if __VERSION__ == 100
|
||||
#define round(x) floor((x) + 0.5)
|
||||
#endif
|
||||
|
||||
float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
|
||||
vec2 ivec2Div(const in vec2 a, const in vec2 b) { return vec2(ivec2(a) / ivec2(b)); }
|
||||
float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
|
||||
@@ -32,13 +36,8 @@ int imod(const in int a, const in int b) { return a - b * (a / b); }
|
||||
|
||||
float pow2(const in float x) { return x * x; }
|
||||
|
||||
const float maxFloat = 10000.0; // NOTE constant also set in TypeScript
|
||||
const float floatLogFactor = 9.210440366976517; // log(maxFloat + 1.0);
|
||||
float encodeFloatLog(const in float value) { return log(value + 1.0) / floatLogFactor; }
|
||||
float decodeFloatLog(const in float value) { return exp(value * floatLogFactor) - 1.0; }
|
||||
|
||||
vec3 encodeFloatRGB(in float value) {
|
||||
value = clamp(value, 0.0, 16777216.0 - 1.0) + 1.0;
|
||||
vec3 packIntToRGB(in float value) {
|
||||
value = clamp(round(value), 0.0, 16777216.0 - 1.0) + 1.0;
|
||||
vec3 c = vec3(0.0);
|
||||
c.b = mod(value, 256.0);
|
||||
value = floor(value / 256.0);
|
||||
@@ -47,8 +46,8 @@ vec3 encodeFloatRGB(in float value) {
|
||||
c.r = mod(value, 256.0);
|
||||
return c / 255.0;
|
||||
}
|
||||
float decodeFloatRGB(const in vec3 rgb) {
|
||||
return (rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0) - 1.0;
|
||||
float unpackRGBToInt(const in vec3 rgb) {
|
||||
return (floor(rgb.r * 255.0 + 0.5) * 256.0 * 256.0 + floor(rgb.g * 255.0 + 0.5) * 256.0 + floor(rgb.b * 255.0 + 0.5)) - 1.0;
|
||||
}
|
||||
|
||||
vec2 packUnitIntervalToRG(const in float v) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const wboit_write = `
|
||||
float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0);
|
||||
gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);
|
||||
// extra alpha is to handle pre-multiplied alpha
|
||||
#if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface)
|
||||
#ifndef dGeometryType_directVolume
|
||||
gl_FragData[1] = vec4((uTransparentBackground ? alpha : 1.0) * alpha * wboitWeight);
|
||||
#else
|
||||
gl_FragData[1] = vec4(alpha * alpha * wboitWeight);
|
||||
|
||||
@@ -35,7 +35,7 @@ uniform float uResolution;
|
||||
|
||||
void main() {
|
||||
vec3 position = readFromTexture(tPosition, SampleID, uGeoTexDim).xyz;
|
||||
float group = decodeFloatRGB(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
|
||||
float group = unpackRGBToInt(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
|
||||
|
||||
position = (aTransform * vec4(position, 1.0)).xyz;
|
||||
gl_PointSize = 7.0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -127,13 +127,9 @@ void main() {
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uView)));
|
||||
vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw));
|
||||
#include apply_light_color
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uView)));
|
||||
vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw));
|
||||
#include apply_light_color
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
@@ -38,7 +38,6 @@ varying vec4 vBoundingSphere;
|
||||
varying mat4 vTransform;
|
||||
|
||||
uniform mat4 uInvView;
|
||||
uniform vec2 uIsoValue;
|
||||
uniform vec3 uGridDim;
|
||||
uniform vec3 uBboxSize;
|
||||
uniform sampler2D tTransferTex;
|
||||
@@ -76,11 +75,9 @@ uniform float uXrayEdgeFalloff;
|
||||
uniform float uInteriorDarkening;
|
||||
uniform bool uInteriorColorFlag;
|
||||
uniform vec3 uInteriorColor;
|
||||
bool interior;
|
||||
|
||||
uniform bool uRenderWboit;
|
||||
uniform bool uDoubleSided;
|
||||
uniform int uPickType;
|
||||
|
||||
uniform float uNear;
|
||||
uniform float uFar;
|
||||
@@ -104,27 +101,22 @@ uniform mat4 uCartnToUnit;
|
||||
uniform sampler3D tGridTex;
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_texture)
|
||||
uniform vec2 uColorTexDim;
|
||||
uniform sampler2D tColor;
|
||||
#endif
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_texture)
|
||||
uniform vec2 uColorTexDim;
|
||||
uniform sampler2D tColor;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
#if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
|
||||
uniform vec2 uOverpaintTexDim;
|
||||
uniform sampler2D tOverpaint;
|
||||
#endif
|
||||
#ifdef dOverpaint
|
||||
#if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
|
||||
uniform vec2 uOverpaintTexDim;
|
||||
uniform sampler2D tOverpaint;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef dSubstance
|
||||
#if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
|
||||
uniform vec2 uSubstanceTexDim;
|
||||
uniform sampler2D tSubstance;
|
||||
#endif
|
||||
#endif
|
||||
#ifdef dUsePalette
|
||||
uniform sampler2D tPalette;
|
||||
#endif
|
||||
|
||||
#if defined(dGridTexType_2d)
|
||||
@@ -148,8 +140,8 @@ float calcDepth(const in vec3 pos) {
|
||||
return 0.5 + 0.5 * clipZW.x / clipZW.y;
|
||||
}
|
||||
|
||||
vec4 transferFunction(float value) {
|
||||
return texture2D(tTransferTex, vec2(value, 0.0));
|
||||
float transferFunction(float value) {
|
||||
return texture2D(tTransferTex, vec2(value, 0.0)).a;
|
||||
}
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
@@ -174,9 +166,7 @@ vec3 v3m4(vec3 p, mat4 m) {
|
||||
float preFogAlphaBlended = 0.0;
|
||||
|
||||
vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
#if defined(dRenderVariant_color) && !defined(dIgnoreLight)
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
|
||||
mat4 cartnToUnit = uCartnToUnit * inverse4(vTransform);
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
mat4 modelTransform = uModel * vTransform * uTransform;
|
||||
@@ -190,21 +180,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
float value = 0.0;
|
||||
vec4 src = vec4(0.0);
|
||||
vec4 dst = vec4(0.0);
|
||||
bool hit = false;
|
||||
float fragmentDepth;
|
||||
|
||||
vec3 posMin = vec3(0.0);
|
||||
vec3 posMax = vec3(1.0) - vec3(1.0) / uGridDim;
|
||||
|
||||
vec3 unitPos;
|
||||
vec3 isoPos;
|
||||
|
||||
vec3 nextPos;
|
||||
float nextValue;
|
||||
|
||||
vec3 color = vec3(0.45, 0.55, 0.8);
|
||||
vec4 overpaint = vec4(0.0);
|
||||
vec3 substance = vec3(0.0);
|
||||
vec4 material;
|
||||
vec4 overpaint;
|
||||
float metalness = uMetalness;
|
||||
float roughness = uRoughness;
|
||||
|
||||
@@ -227,7 +214,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
if (unitPos.x > posMax.x || unitPos.y > posMax.y || unitPos.z > posMax.z ||
|
||||
unitPos.x < posMin.x || unitPos.y < posMin.y || unitPos.z < posMin.z
|
||||
) {
|
||||
if (hit) break;
|
||||
prevValue = value;
|
||||
pos += step;
|
||||
continue;
|
||||
@@ -246,226 +232,104 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(dRenderMode_isosurface)
|
||||
if (prevValue > 0.0 && ( // there was a prev Value
|
||||
(prevValue < uIsoValue.x && value > uIsoValue.x) || // entering isosurface
|
||||
(prevValue > uIsoValue.x && value < uIsoValue.x) // leaving isosurface
|
||||
)) {
|
||||
isoPos = v3m4(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)))), cartnToUnit);
|
||||
vec4 mvPosition = modelViewTransform * vec4(unitPos * uGridDim, 1.0);
|
||||
if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
|
||||
break;
|
||||
|
||||
vec4 mvPosition = modelViewTransform * vec4(isoPos * uGridDim, 1.0);
|
||||
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
vec3 vModelPosition = v3m4(isoPos * uGridDim, modelTransform);
|
||||
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
|
||||
prevValue = value;
|
||||
pos += step;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
float depth = calcDepth(mvPosition.xyz);
|
||||
if (depth > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
|
||||
break;
|
||||
|
||||
#ifdef enabledFragDepth
|
||||
if (!hit) {
|
||||
gl_FragDepthEXT = depth;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (uPickType == 1) {
|
||||
return vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
} else if (uPickType == 2) {
|
||||
return vec4(encodeFloatRGB(vInstance), 1.0);
|
||||
} else {
|
||||
#ifdef dPackedGroup
|
||||
return vec4(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb, 1.0);
|
||||
#else
|
||||
vec3 g = floor(isoPos * uGridDim + 0.5);
|
||||
return vec4(encodeFloatRGB(g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y), 1.0);
|
||||
#endif
|
||||
}
|
||||
#elif defined(dRenderVariant_depth)
|
||||
#ifdef enabledFragDepth
|
||||
return packDepthToRGBA(gl_FragDepthEXT);
|
||||
#else
|
||||
return packDepthToRGBA(depth);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dPackedGroup
|
||||
float group = decodeFloatRGB(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb);
|
||||
#else
|
||||
vec3 g = floor(isoPos * uGridDim + 0.5);
|
||||
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_uniform)
|
||||
color = uColor;
|
||||
#elif defined(dColorType_instance)
|
||||
color = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_group)
|
||||
color = readFromTexture(tColor, group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_groupInstance)
|
||||
color = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_vertex)
|
||||
color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
|
||||
#elif defined(dColorType_vertexInstance)
|
||||
color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
#if defined(dOverpaintType_groupInstance)
|
||||
overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
|
||||
#elif defined(dOverpaintType_vertexInstance)
|
||||
overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount));
|
||||
#endif
|
||||
|
||||
color = mix(color, overpaint.rgb, overpaint.a);
|
||||
#endif
|
||||
|
||||
// handle flipping and negative isosurfaces
|
||||
#ifdef dFlipSided
|
||||
bool flipped = value < uIsoValue.y; // flipped
|
||||
#else
|
||||
bool flipped = value > uIsoValue.y;
|
||||
#endif
|
||||
interior = value < uIsoValue.x && flipped;
|
||||
if (uDoubleSided) {
|
||||
if (interior) {
|
||||
prevValue = value;
|
||||
pos += step;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
vec3 vViewPosition = mvPosition.xyz;
|
||||
vec4 material = vec4(color, uAlpha);
|
||||
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#if defined(dFlatShaded)
|
||||
// nearest grid point
|
||||
isoPos = floor(isoPos * uGridDim + 0.5) / uGridDim;
|
||||
#endif
|
||||
#ifdef dPackedGroup
|
||||
// compute gradient by central differences
|
||||
gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
|
||||
gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
|
||||
gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a;
|
||||
#else
|
||||
gradient = textureVal(isoPos).xyz * 2.0 - 1.0;
|
||||
#endif
|
||||
vec3 normal = -normalize(normalMatrix * normalize(gradient));
|
||||
normal = normal * (float(flipped) * 2.0 - 1.0);
|
||||
normal = normal * -(float(interior) * 2.0 - 1.0);
|
||||
#ifdef dSubstance
|
||||
#if defined(dSubstanceType_groupInstance)
|
||||
substance = readFromTexture(tSubstance, vInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
|
||||
#elif defined(dSubstanceType_vertexInstance)
|
||||
substance = texture3dFrom1dTrilinear(tSubstance, isoPos, uGridDim, uSubstanceTexDim, vInstance * float(uVertexCount)).rgb;
|
||||
#endif
|
||||
metalness = mix(metalness, substance.r, substance.b);
|
||||
roughness = mix(roughness, substance.g, substance.b);
|
||||
#endif
|
||||
#include apply_light_color
|
||||
#endif
|
||||
|
||||
float marker = uMarker;
|
||||
if (uMarker == -1.0) {
|
||||
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
}
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
|
||||
fragmentDepth = depth;
|
||||
#include apply_fog
|
||||
|
||||
src = gl_FragColor;
|
||||
|
||||
if (!uTransparentBackground) {
|
||||
// done in 'apply_fog' otherwise
|
||||
src.rgb *= src.a;
|
||||
}
|
||||
dst = (1.0 - dst.a) * src + dst; // standard blending
|
||||
#endif
|
||||
|
||||
#ifdef dSingleLayer
|
||||
break;
|
||||
#endif
|
||||
|
||||
hit = true;
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
|
||||
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
|
||||
prevValue = value;
|
||||
pos += step;
|
||||
continue;
|
||||
}
|
||||
prevValue = value;
|
||||
#elif defined(dRenderMode_volume)
|
||||
vec4 mvPosition = modelViewTransform * vec4(unitPos * uGridDim, 1.0);
|
||||
if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
|
||||
if (clipTest(vec4(vModelPosition, 0.0), 0)) {
|
||||
prevValue = value;
|
||||
pos += step;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
vec3 vViewPosition = mvPosition.xyz;
|
||||
material.a = transferFunction(value);
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
vec3 vViewPosition = mvPosition.xyz;
|
||||
vec4 material = transferFunction(value);
|
||||
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor.rgb = material.rgb;
|
||||
#else
|
||||
if (material.a >= 0.01) {
|
||||
#ifdef dPackedGroup
|
||||
// compute gradient by central differences
|
||||
gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
|
||||
gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
|
||||
gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
|
||||
#else
|
||||
gradient = cell.xyz * 2.0 - 1.0;
|
||||
#endif
|
||||
vec3 normal = -normalize(normalMatrix * normalize(gradient));
|
||||
#include apply_light_color
|
||||
} else {
|
||||
gl_FragColor.rgb = material.rgb;
|
||||
}
|
||||
#endif
|
||||
|
||||
gl_FragColor.a = material.a * uAlpha * uTransferScale;
|
||||
|
||||
float marker = uMarker;
|
||||
if (uMarker == -1.0) {
|
||||
#ifdef dPackedGroup
|
||||
float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
|
||||
#else
|
||||
vec3 g = floor(unitPos * uGridDim + 0.5);
|
||||
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
|
||||
#endif
|
||||
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
}
|
||||
#include apply_marker_color
|
||||
|
||||
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
|
||||
fragmentDepth = calcDepth(mvPosition.xyz);
|
||||
#include apply_fog
|
||||
|
||||
src = gl_FragColor;
|
||||
|
||||
if (!uTransparentBackground) {
|
||||
// done in 'apply_fog' otherwise
|
||||
src.rgb *= src.a;
|
||||
}
|
||||
dst = (1.0 - dst.a) * src + dst; // standard blending
|
||||
#ifdef dPackedGroup
|
||||
float group = unpackRGBToInt(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
|
||||
#else
|
||||
vec3 g = floor(unitPos * uGridDim + 0.5);
|
||||
// note that we swap x and z because the texture is flipped around y
|
||||
#if defined(dAxisOrder_012)
|
||||
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y; // 210
|
||||
#elif defined(dAxisOrder_021)
|
||||
float group = g.y + g.z * uGridDim.y + g.x * uGridDim.y * uGridDim.z; // 120
|
||||
#elif defined(dAxisOrder_102)
|
||||
float group = g.z + g.x * uGridDim.z + g.y * uGridDim.z * uGridDim.x; // 201
|
||||
#elif defined(dAxisOrder_120)
|
||||
float group = g.x + g.z * uGridDim.x + g.y * uGridDim.x * uGridDim.z; // 021
|
||||
#elif defined(dAxisOrder_201)
|
||||
float group = g.y + g.x * uGridDim.y + g.z * uGridDim.y * uGridDim.x; // 102
|
||||
#elif defined(dAxisOrder_210)
|
||||
float group = g.x + g.y * uGridDim.x + g.z * uGridDim.x * uGridDim.y; // 012
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_direct) && defined(dUsePalette)
|
||||
material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
|
||||
#elif defined(dColorType_uniform)
|
||||
material.rgb = uColor;
|
||||
#elif defined(dColorType_instance)
|
||||
material.rgb = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_group)
|
||||
material.rgb = readFromTexture(tColor, group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_groupInstance)
|
||||
material.rgb = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_vertex)
|
||||
material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
|
||||
#elif defined(dColorType_vertexInstance)
|
||||
material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
#if defined(dOverpaintType_groupInstance)
|
||||
overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
|
||||
#elif defined(dOverpaintType_vertexInstance)
|
||||
overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount));
|
||||
#endif
|
||||
|
||||
material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
|
||||
#endif
|
||||
|
||||
if (material.a >= 0.01) {
|
||||
#ifdef dPackedGroup
|
||||
// compute gradient by central differences
|
||||
gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
|
||||
gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
|
||||
gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
|
||||
#else
|
||||
gradient = cell.xyz * 2.0 - 1.0;
|
||||
#endif
|
||||
vec3 normal = -normalize(normalMatrix * normalize(gradient));
|
||||
#include apply_light_color
|
||||
} else {
|
||||
gl_FragColor.rgb = material.rgb;
|
||||
}
|
||||
|
||||
gl_FragColor.a = material.a * uAlpha * uTransferScale;
|
||||
|
||||
float marker = uMarker;
|
||||
if (uMarker == -1.0) {
|
||||
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
}
|
||||
#include apply_marker_color
|
||||
|
||||
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
|
||||
fragmentDepth = calcDepth(mvPosition.xyz);
|
||||
#include apply_fog
|
||||
|
||||
src = gl_FragColor;
|
||||
|
||||
if (!uTransparentBackground) {
|
||||
// done in 'apply_fog' otherwise
|
||||
src.rgb *= src.a;
|
||||
}
|
||||
dst = (1.0 - dst.a) * src + dst; // standard blending
|
||||
|
||||
// break if the color is opaque enough
|
||||
if (dst.a > 0.95)
|
||||
break;
|
||||
@@ -473,12 +337,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
pos += step;
|
||||
}
|
||||
|
||||
#if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
|
||||
// ensure depth is written everywhere
|
||||
if (!hit)
|
||||
gl_FragDepthEXT = 1.0;
|
||||
#endif
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -489,21 +347,6 @@ void main() {
|
||||
if (gl_FrontFacing)
|
||||
discard;
|
||||
|
||||
#ifdef dRenderVariant_marking
|
||||
// not supported
|
||||
discard;
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
|
||||
#if defined(dRenderMode_volume)
|
||||
// always ignore pick & depth for volume
|
||||
discard;
|
||||
#elif defined(dRenderMode_isosurface)
|
||||
if (uAlpha < uPickingAlphaThreshold)
|
||||
discard; // ignore so the element below can be picked
|
||||
#endif
|
||||
#endif
|
||||
|
||||
vec3 rayDir = mix(normalize(vOrigPos - uCameraPosition), uCameraDir, uIsOrtho);
|
||||
vec3 step = rayDir * uStepScale;
|
||||
|
||||
@@ -512,21 +355,9 @@ void main() {
|
||||
vec3 start = mix(uCameraPosition, vOrigPos, uIsOrtho) + (d * rayDir);
|
||||
gl_FragColor = raymarch(start, step, rayDir);
|
||||
|
||||
#if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
|
||||
// discard when nothing was hit
|
||||
if (gl_FragColor == vec4(0.0))
|
||||
discard;
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#else
|
||||
float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
|
||||
#endif
|
||||
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
|
||||
interior = false;
|
||||
#include wboit_write
|
||||
#endif
|
||||
float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
|
||||
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
|
||||
bool interior = false;
|
||||
#include wboit_write
|
||||
}
|
||||
`;
|
||||
@@ -51,7 +51,7 @@ void main() {
|
||||
#endif
|
||||
if (dist * uRadiusFactorInv > minDistance + uResolution * 0.05)
|
||||
discard;
|
||||
gl_FragColor.rgb = encodeFloatRGB(vGroup);
|
||||
gl_FragColor.rgb = packIntToRGB(vGroup);
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
@@ -33,12 +33,12 @@ void main(void) {
|
||||
c = texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0;
|
||||
d = texture2D(tInputLevel, position + vec2(k, k)).r * 255.0;
|
||||
} else {
|
||||
a = decodeFloatRGB(texture2D(tPreviousLevel, position).rgb);
|
||||
b = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
|
||||
c = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
|
||||
d = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
|
||||
a = unpackRGBToInt(texture2D(tPreviousLevel, position).rgb);
|
||||
b = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
|
||||
c = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
|
||||
d = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
|
||||
}
|
||||
gl_FragColor = vec4(encodeFloatRGB(a + b + c + d), 1.0);
|
||||
gl_FragColor = vec4(packIntToRGB(a + b + c + d), 1.0);
|
||||
#else
|
||||
int a, b, c, d;
|
||||
|
||||
|
||||
@@ -105,9 +105,9 @@ void main() {
|
||||
if (imageData.a < 0.3)
|
||||
discard;
|
||||
if (uPickType == 1) {
|
||||
gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
|
||||
} else if (uPickType == 2) {
|
||||
gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0);
|
||||
gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
|
||||
} else {
|
||||
gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
|
||||
}
|
||||
@@ -118,7 +118,7 @@ void main() {
|
||||
#elif defined(dRenderVariant_marking)
|
||||
float marker = uMarker;
|
||||
if (uMarker == -1.0) {
|
||||
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
|
||||
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
|
||||
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
}
|
||||
@@ -144,7 +144,7 @@ void main() {
|
||||
|
||||
float marker = uMarker;
|
||||
if (uMarker == -1.0) {
|
||||
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
|
||||
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
|
||||
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ int idot4(const in ivec4 a, const in ivec4 b) {
|
||||
|
||||
#if __VERSION__ == 100
|
||||
int pyramidVoxel(vec2 pos) {
|
||||
return int(decodeFloatRGB(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
|
||||
return int(unpackRGBToInt(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
|
||||
}
|
||||
#else
|
||||
int pyramidVoxel(vec2 pos) {
|
||||
@@ -86,6 +86,25 @@ vec4 baseVoxel(vec2 pos) {
|
||||
return texture2D(tActiveVoxelsBase, pos / uSize);
|
||||
}
|
||||
|
||||
vec4 getGroup(const in vec3 p) {
|
||||
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
|
||||
// note that we swap x and z because the texture is flipped around y
|
||||
#if defined(dAxisOrder_012)
|
||||
float group = p.z + p.y * gridDim.z + p.x * gridDim.z * gridDim.y; // 210
|
||||
#elif defined(dAxisOrder_021)
|
||||
float group = p.y + p.z * gridDim.y + p.x * gridDim.y * gridDim.z; // 120
|
||||
#elif defined(dAxisOrder_102)
|
||||
float group = p.z + p.x * gridDim.z + p.y * gridDim.z * gridDim.x; // 201
|
||||
#elif defined(dAxisOrder_120)
|
||||
float group = p.x + p.z * gridDim.x + p.y * gridDim.x * gridDim.z; // 021
|
||||
#elif defined(dAxisOrder_201)
|
||||
float group = p.y + p.x * gridDim.y + p.z * gridDim.y * gridDim.x; // 102
|
||||
#elif defined(dAxisOrder_210)
|
||||
float group = p.x + p.y * gridDim.x + p.z * gridDim.x * gridDim.y; // 012
|
||||
#endif
|
||||
return vec4(group > 16777215.5 ? vec3(1.0) : packIntToRGB(group), 1.0);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
// get 1D index
|
||||
int vI = int(gl_FragCoord.x) + int(gl_FragCoord.y) * int(uSize);
|
||||
@@ -255,18 +274,13 @@ void main(void) {
|
||||
#ifdef dPackedGroup
|
||||
gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0);
|
||||
#else
|
||||
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
|
||||
float group = coord3d.z + coord3d.y * gridDim.z + coord3d.x * gridDim.z * gridDim.y;
|
||||
gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
|
||||
gl_FragData[1] = getGroup(coord3d);
|
||||
#endif
|
||||
#else
|
||||
#ifdef dPackedGroup
|
||||
gl_FragData[1] = vec4(t < 0.5 ? d0.rgb : d1.rgb, 1.0);
|
||||
#else
|
||||
vec3 b = t < 0.5 ? b0 : b1;
|
||||
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
|
||||
float group = b.z + b.y * gridDim.z + b.x * gridDim.z * gridDim.y;
|
||||
gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
|
||||
gl_FragData[1] = getGroup(t < 0.5 ? b0 : b1);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -43,17 +43,13 @@ void main() {
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#if defined(dFlatShaded)
|
||||
vec3 normal = -faceNormal;
|
||||
#else
|
||||
#if defined(dFlatShaded)
|
||||
vec3 normal = -faceNormal;
|
||||
#else
|
||||
vec3 normal = -normalize(vNormal);
|
||||
if (uDoubleSided) normal *= float(frontFacing) * 2.0 - 1.0;
|
||||
#endif
|
||||
#include apply_light_color
|
||||
vec3 normal = -normalize(vNormal);
|
||||
if (uDoubleSided) normal *= float(frontFacing) * 2.0 - 1.0;
|
||||
#endif
|
||||
#include apply_light_color
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -16,7 +16,7 @@ precision highp sampler2D;
|
||||
#include common_clip
|
||||
#include texture3d_from_2d_linear
|
||||
|
||||
#ifdef dGeoTexture
|
||||
#ifdef dGeometryType_textureMesh
|
||||
uniform vec2 uGeoTexDim;
|
||||
uniform sampler2D tPosition;
|
||||
uniform sampler2D tGroup;
|
||||
@@ -39,7 +39,7 @@ void main(){
|
||||
#include assign_color_varying
|
||||
#include clip_instance
|
||||
|
||||
#ifdef dGeoTexture
|
||||
#ifdef dGeometryType_textureMesh
|
||||
vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 normal = aNormal;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -24,8 +24,7 @@ uniform vec3 uFogColor;
|
||||
uniform vec3 uOutlineColor;
|
||||
uniform bool uTransparentBackground;
|
||||
|
||||
uniform float uOcclusionBias;
|
||||
uniform float uOcclusionRadius;
|
||||
uniform vec2 uOcclusionOffset;
|
||||
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
@@ -102,7 +101,7 @@ void main(void) {
|
||||
if (!isBackground(depth)) {
|
||||
viewDist = abs(getViewZ(depth));
|
||||
fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
|
||||
float occlusionFactor = getSsao(coords);
|
||||
float occlusionFactor = getSsao(coords + uOcclusionOffset);
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(mix(occlusionColor, uFogColor, fogFactor), color.rgb, occlusionFactor);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -92,12 +92,8 @@ void main(void){
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
vec3 normal = -cameraNormal;
|
||||
#include apply_light_color
|
||||
#endif
|
||||
vec3 normal = -cameraNormal;
|
||||
#include apply_light_color
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -176,7 +176,7 @@ function isTexture2d(x: TextureImage<any> | TextureVolume<any>, target: number,
|
||||
return target === gl.TEXTURE_2D;
|
||||
}
|
||||
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureImage<any> {
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureVolume<any> {
|
||||
return target === gl.TEXTURE_3D;
|
||||
}
|
||||
|
||||
@@ -260,6 +260,10 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
let destroyed = false;
|
||||
|
||||
function define(_width: number, _height: number, _depth?: number) {
|
||||
if (_width === 0 || _height === 0 || (isWebGL2(gl) && target === gl.TEXTURE_3D && _depth === 0)) {
|
||||
throw new Error('empty textures are not allowed');
|
||||
}
|
||||
|
||||
if (width === _width && height === _height && depth === (_depth || 0)) return;
|
||||
|
||||
width = _width, height = _height, depth = _depth || 0;
|
||||
@@ -272,14 +276,20 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
throw new Error('unknown texture target');
|
||||
}
|
||||
}
|
||||
define(1, 1, isWebGL2(gl) && target === gl.TEXTURE_3D ? 1 : 0);
|
||||
|
||||
function load(data: TextureImage<any> | TextureVolume<any> | HTMLImageElement, sub = false) {
|
||||
if (data.width === 0 || data.height === 0 || (!isImage(data) && isWebGL2(gl) && isTexture3d(data, target, gl) && data.depth === 0)) {
|
||||
throw new Error('empty textures are not allowed');
|
||||
}
|
||||
|
||||
gl.bindTexture(target, texture);
|
||||
// unpack alignment of 1 since we use textures only for data
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
||||
if (isImage(data)) {
|
||||
width = data.width, height = data.height;
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
|
||||
|
||||
28
src/mol-io/common/_spec/encoder.spec.ts
Normal file
28
src/mol-io/common/_spec/encoder.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ArrayEncoding } from '../binary-cif/array-encoder';
|
||||
import { decode } from '../binary-cif/decoder';
|
||||
|
||||
const E = ArrayEncoding;
|
||||
|
||||
test('fixedPoint2', async () => {
|
||||
const fixedPoint2 = E.by(E.fixedPoint(100)).and(E.delta).and(E.integerPacking);
|
||||
|
||||
const x = [1.092, 1.960, 0.666, 0.480, 1.267];
|
||||
const y = [7.428, 7.026, 6.851, 7.524, 8.333];
|
||||
const z = [26.270, 26.561, 25.573, 27.055, 25.881];
|
||||
|
||||
const xEnc = fixedPoint2.encode(new Float32Array(x));
|
||||
const yEnc = fixedPoint2.encode(new Float32Array(y));
|
||||
const zEnc = fixedPoint2.encode(new Float32Array(z));
|
||||
|
||||
expect(xEnc.data.length).toEqual(6);
|
||||
expect(yEnc.data.length).toEqual(5);
|
||||
expect(zEnc.data.length).toEqual(6);
|
||||
|
||||
const xDec = decode(xEnc);
|
||||
const yDec = decode(yEnc);
|
||||
const zDec = decode(zEnc);
|
||||
|
||||
x.forEach((a, i) => expect(xDec[i]).toBeCloseTo(a, 2));
|
||||
y.forEach((a, i) => expect(yDec[i]).toBeCloseTo(a, 2));
|
||||
z.forEach((a, i) => expect(zDec[i]).toBeCloseTo(a, 2));
|
||||
});
|
||||
@@ -1,5 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
*/
|
||||
|
||||
import { parseMol } from '../mol/parser';
|
||||
import { parseMol, formalChargeMapper } from '../mol/parser';
|
||||
|
||||
const MolString = `2244
|
||||
-OEChem-04072009073D
|
||||
@@ -49,6 +56,48 @@ const MolString = `2244
|
||||
13 20 1 0 0 0 0
|
||||
M END`;
|
||||
|
||||
const MolStringWithAtomBlockCharge = `
|
||||
Ketcher 1 72215442D 1 1.00000 0.00000 0
|
||||
|
||||
4 3 0 0 0 0 999 V2000
|
||||
0.0000 0.0000 0.0000 C 0 1 0 0 0 0 0 0 0 0 0 0
|
||||
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 4 2 0 0 0 0
|
||||
3 1 1 0 0 0 0
|
||||
2 1 1 0 0 0 0
|
||||
M END`;
|
||||
|
||||
const MolStringWithPropertyBlockCharge = `
|
||||
Ketcher 1 72215442D 1 1.00000 0.00000 0
|
||||
|
||||
4 3 0 0 0 0 999 V2000
|
||||
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 4 2 0 0 0 0
|
||||
3 1 1 0 0 0 0
|
||||
2 1 1 0 0 0 0
|
||||
M CHG 3 2 -1 3 1 4 1
|
||||
M END`;
|
||||
|
||||
const MolStringWithMultipleChargeLines = `
|
||||
Ketcher 1 72215442D 1 1.00000 0.00000 0
|
||||
|
||||
4 3 0 0 0 0 999 V2000
|
||||
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 4 2 0 0 0 0
|
||||
3 1 1 0 0 0 0
|
||||
2 1 1 0 0 0 0
|
||||
M CHG 1 2 -1
|
||||
M CHG 2 3 1 4 1
|
||||
M END`;
|
||||
|
||||
describe('mol reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await parseMol(MolString).run();
|
||||
@@ -70,4 +119,63 @@ describe('mol reader', () => {
|
||||
expect(bonds.atomIdxB.value(20)).toBe(20);
|
||||
expect(bonds.order.value(20)).toBe(1);
|
||||
});
|
||||
it('property block charges', async () => {
|
||||
const parsed = await parseMol(MolStringWithPropertyBlockCharge).run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
const { formalCharges } = parsed.result;
|
||||
|
||||
expect(formalCharges.atomIdx.rowCount).toBe(3);
|
||||
expect(formalCharges.charge.rowCount).toBe(3);
|
||||
|
||||
expect(formalCharges.atomIdx.value(0)).toBe(2);
|
||||
expect(formalCharges.atomIdx.value(1)).toBe(3);
|
||||
|
||||
expect(formalCharges.charge.value(0)).toBe(-1);
|
||||
expect(formalCharges.charge.value(1)).toBe(1);
|
||||
});
|
||||
it('multiple charge lines', async () => {
|
||||
const parsed = await parseMol(MolStringWithMultipleChargeLines).run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
const { formalCharges } = parsed.result;
|
||||
|
||||
expect(formalCharges.atomIdx.rowCount).toBe(3);
|
||||
expect(formalCharges.charge.rowCount).toBe(3);
|
||||
|
||||
expect(formalCharges.atomIdx.value(0)).toBe(2);
|
||||
expect(formalCharges.atomIdx.value(1)).toBe(3);
|
||||
|
||||
expect(formalCharges.charge.value(0)).toBe(-1);
|
||||
expect(formalCharges.charge.value(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('atom block charge mapping', async () => {
|
||||
expect(formalChargeMapper(7)).toBe(-3);
|
||||
expect(formalChargeMapper(6)).toBe(-2);
|
||||
expect(formalChargeMapper(5)).toBe(-1);
|
||||
expect(formalChargeMapper(0)).toBe(0);
|
||||
expect(formalChargeMapper(3)).toBe(1);
|
||||
expect(formalChargeMapper(2)).toBe(2);
|
||||
expect(formalChargeMapper(1)).toBe(3);
|
||||
expect(formalChargeMapper(4)).toBe(0);
|
||||
});
|
||||
it('atom block charges', async () => {
|
||||
const parsed = await parseMol(MolStringWithAtomBlockCharge).run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
const { atoms, formalCharges } = parsed.result;
|
||||
|
||||
/* No property block charges */
|
||||
expect(formalCharges.atomIdx.rowCount).toBe(0);
|
||||
expect(formalCharges.charge.rowCount).toBe(0);
|
||||
|
||||
expect(atoms.formal_charge.value(0)).toBe(1);
|
||||
expect(atoms.formal_charge.value(1)).toBe(0);
|
||||
expect(atoms.formal_charge.value(2)).toBe(0);
|
||||
expect(atoms.formal_charge.value(3)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -244,6 +244,84 @@ GASTEIGER
|
||||
25 13 23 1
|
||||
26 13 24 1`;
|
||||
|
||||
const Mol2StringCrysin = `@<TRIPOS>MOLECULE
|
||||
1144204
|
||||
12 11 2 0 0
|
||||
SMALL
|
||||
USER_CHARGES
|
||||
****
|
||||
Generated from the CSD
|
||||
|
||||
@<TRIPOS>ATOM
|
||||
1 Cl1 0.0925 3.6184 1.9845 Cl 1 RES1 -1.0000
|
||||
2 C1 -4.7391 0.3350 0.4215 C.ar 2 RES2 0.0000
|
||||
3 C2 -3.4121 0.2604 0.9351 C.ar 2 RES2 0.0000
|
||||
4 C3 -2.9169 1.2555 1.7726 C.ar 2 RES2 0.0000
|
||||
5 C4 -3.7118 2.3440 2.1099 C.ar 2 RES2 0.0000
|
||||
6 C5 -5.0314 2.4052 1.6209 C.ar 2 RES2 0.0000
|
||||
7 C6 -5.5372 1.4057 0.7962 C.ar 2 RES2 0.0000
|
||||
8 C7 -6.9925 1.4547 0.3334 C.3 2 RES2 0.0000
|
||||
9 C8 -7.8537 0.5554 1.1859 C.3 2 RES2 0.0000
|
||||
10 N1 -9.3089 0.7134 0.8192 N.3 2 RES2 1.0000
|
||||
11 O1 -2.6613 -0.8147 0.5707 O.3 2 RES2 0.0000
|
||||
12 O2 -1.6204 1.0919 2.2584 O.3 2 RES2 0.0000
|
||||
@<TRIPOS>BOND
|
||||
1 2 3 ar
|
||||
2 3 4 ar
|
||||
3 4 5 ar
|
||||
4 5 6 ar
|
||||
5 6 7 ar
|
||||
6 7 2 ar
|
||||
7 8 7 1
|
||||
8 9 8 1
|
||||
9 10 9 1
|
||||
10 11 3 1
|
||||
11 12 4 1
|
||||
@<TRIPOS>SUBSTRUCTURE
|
||||
1 RES1 1 GROUP 0 **** **** 0
|
||||
2 RES2 2 GROUP 0 **** **** 0
|
||||
@<TRIPOS>CRYSIN
|
||||
10.5150 11.1300 7.9380 90.0000 90.0000 90.0000 29 5
|
||||
@<TRIPOS>MOLECULE
|
||||
1144204
|
||||
12 11 2 0 0
|
||||
SMALL
|
||||
USER_CHARGES
|
||||
****
|
||||
Generated from the CSD
|
||||
|
||||
@<TRIPOS>ATOM
|
||||
1 Cl1 0.0925 3.6184 1.9845 Cl 1 RES1 -1.0000
|
||||
2 C1 -4.7391 0.3350 0.4215 C.ar 2 RES2 0.0000
|
||||
3 C2 -3.4121 0.2604 0.9351 C.ar 2 RES2 0.0000
|
||||
4 C3 -2.9169 1.2555 1.7726 C.ar 2 RES2 0.0000
|
||||
5 C4 -3.7118 2.3440 2.1099 C.ar 2 RES2 0.0000
|
||||
6 C5 -5.0314 2.4052 1.6209 C.ar 2 RES2 0.0000
|
||||
7 C6 -5.5372 1.4057 0.7962 C.ar 2 RES2 0.0000
|
||||
8 C7 -6.9925 1.4547 0.3334 C.3 2 RES2 0.0000
|
||||
9 C8 -7.8537 0.5554 1.1859 C.3 2 RES2 0.0000
|
||||
10 N1 -9.3089 0.7134 0.8192 N.3 2 RES2 1.0000
|
||||
11 O1 -2.6613 -0.8147 0.5707 O.3 2 RES2 0.0000
|
||||
12 O2 -1.6204 1.0919 2.2584 O.3 2 RES2 0.0000
|
||||
@<TRIPOS>BOND
|
||||
1 2 3 ar
|
||||
2 3 4 ar
|
||||
3 4 5 ar
|
||||
4 5 6 ar
|
||||
5 6 7 ar
|
||||
6 7 2 ar
|
||||
7 8 7 1
|
||||
8 9 8 1
|
||||
9 10 9 1
|
||||
10 11 3 1
|
||||
11 12 4 1
|
||||
@<TRIPOS>SUBSTRUCTURE
|
||||
1 RES1 1 GROUP 0 **** **** 0
|
||||
2 RES2 2 GROUP 0 **** **** 0
|
||||
@<TRIPOS>CRYSIN
|
||||
10.5150 11.1300 7.9380 90.0000 90.0000 90.0000 29 5
|
||||
`;
|
||||
|
||||
describe('mol2 reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await parseMol2(Mol2String, '').run();
|
||||
@@ -397,4 +475,29 @@ describe('mol2 reader', () => {
|
||||
// optional bond fields
|
||||
expect(bonds.status_bits.value(0)).toBe('');
|
||||
});
|
||||
|
||||
it('crysin', async () => {
|
||||
const parsed = await parseMol2(Mol2StringCrysin, '').run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
const mol2File = parsed.result;
|
||||
|
||||
// number of structures
|
||||
expect(mol2File.structures.length).toBe(2);
|
||||
|
||||
// crysin fields
|
||||
for (const data of mol2File.structures) {
|
||||
expect(data.crysin).toEqual({
|
||||
a: 10.5150,
|
||||
b: 11.1300,
|
||||
c: 7.9380,
|
||||
alpha: 90.0,
|
||||
beta: 90.0,
|
||||
gamma: 90.0,
|
||||
spaceGroup: 29,
|
||||
setting: 5
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
*/
|
||||
|
||||
import { parseSdf } from '../sdf/parser';
|
||||
|
||||
@@ -458,6 +465,38 @@ describe('sdf reader', () => {
|
||||
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
|
||||
});
|
||||
|
||||
it('charge parsing in V2000', async () => {
|
||||
const parsed = await parseSdf(SdfString).run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
const compound1 = parsed.result.compounds[0];
|
||||
const compound2 = parsed.result.compounds[1];
|
||||
const compound3 = parsed.result.compounds[2];
|
||||
|
||||
const formalCharges1 = {
|
||||
atomIdx: compound1.molFile.formalCharges.atomIdx,
|
||||
charge: compound1.molFile.formalCharges.charge
|
||||
};
|
||||
const formalCharges2 = {
|
||||
atomIdx: compound2.molFile.formalCharges.atomIdx,
|
||||
charge: compound2.molFile.formalCharges.charge
|
||||
};
|
||||
const formalCharges3 = {
|
||||
atomIdx: compound3.molFile.formalCharges.atomIdx,
|
||||
charge: compound3.molFile.formalCharges.charge
|
||||
};
|
||||
|
||||
expect(formalCharges1.atomIdx.rowCount).toBe(3);
|
||||
expect(formalCharges2.atomIdx.rowCount).toBe(3);
|
||||
expect(formalCharges3.atomIdx.rowCount).toBe(0);
|
||||
|
||||
expect(formalCharges1.charge.rowCount === formalCharges1.atomIdx.rowCount).toBe(true);
|
||||
expect(formalCharges2.charge.rowCount === formalCharges2.atomIdx.rowCount).toBe(true);
|
||||
expect(formalCharges3.charge.rowCount === formalCharges3.atomIdx.rowCount).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('v3000', async () => {
|
||||
const parsed = await parseSdf(V3000SdfString).run();
|
||||
if (parsed.isError) {
|
||||
@@ -486,6 +525,11 @@ describe('sdf reader', () => {
|
||||
expect(compound1.molFile.bonds.atomIdxB.value(10)).toBe(9);
|
||||
expect(compound1.molFile.bonds.order.value(10)).toBe(2);
|
||||
|
||||
expect(compound1.molFile.formalCharges.atomIdx.rowCount).toBe(13);
|
||||
for (let i = 0; i < compound1.molFile.atoms.count; i++) {
|
||||
expect(compound1.molFile.formalCharges.charge.value(i)).toBe(0);
|
||||
}
|
||||
|
||||
expect(compound1.dataItems.dataHeader.rowCount).toBe(2);
|
||||
expect(compound1.dataItems.data.rowCount).toBe(2);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.353, IHM 1.17, MA 1.3.3.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -215,6 +215,28 @@ export const mmCIF_Schema = {
|
||||
* formal charge assignment normally found in chemical diagrams.
|
||||
*/
|
||||
pdbx_formal_charge: int,
|
||||
/**
|
||||
* This data item is an ordinal which identifies distinct chemical components in the atom_site category, both
|
||||
* polymeric and non-polymeric.
|
||||
*/
|
||||
pdbx_label_index: int,
|
||||
/**
|
||||
* The name of additional external databases with residue level mapping.
|
||||
*/
|
||||
pdbx_sifts_xref_db_name: str,
|
||||
/**
|
||||
* The accession code related to the additional external database entry.
|
||||
*/
|
||||
pdbx_sifts_xref_db_acc: str,
|
||||
/**
|
||||
* The sequence position of the external database entry that corresponds
|
||||
* to the residue mapping defined by the SIFTS process.
|
||||
*/
|
||||
pdbx_sifts_xref_db_num: str,
|
||||
/**
|
||||
* Describes the residue type of the given UniProt match
|
||||
*/
|
||||
pdbx_sifts_xref_db_res: str,
|
||||
/**
|
||||
* The model id corresponding to the atom site.
|
||||
* This data item is a pointer to _ihm_model_list.model_id
|
||||
@@ -682,7 +704,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* An abbreviation that identifies the database.
|
||||
*/
|
||||
database_id: Aliased<'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'MDF' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | 'EBI' | 'PDBE' | 'BMRB' | 'WWPDB' | 'PDB_ACC'>(str),
|
||||
database_id: Aliased<'AlphaFoldDB' | 'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'ModelArchive' | 'MDF' | 'MODBASE' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | 'SWISS-MODEL_REPOSITORY' | 'EBI' | 'PDBE' | 'BMRB' | 'WWPDB' | 'PDB_ACC'>(str),
|
||||
/**
|
||||
* The code assigned by the database identified in
|
||||
* _database_2.database_id.
|
||||
@@ -2211,6 +2233,10 @@ export const mmCIF_Schema = {
|
||||
oligomeric_count: int,
|
||||
/**
|
||||
* A description of special aspects of the macromolecular assembly.
|
||||
*
|
||||
* In the PDB, 'representative helical assembly', 'complete point assembly',
|
||||
* 'complete icosahedral assembly', 'software_defined_assembly', 'author_defined_assembly',
|
||||
* and 'author_and_software_defined_assembly' are considered "biologically relevant assemblies.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../../mol-data/db';
|
||||
@@ -10,6 +11,7 @@ import { TokenColumnProvider as TokenColumn } from '../common/text/column/token'
|
||||
import { TokenBuilder, Tokenizer } from '../common/text/tokenizer';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
|
||||
|
||||
/** Subset of the MolFile V2000 format */
|
||||
export interface MolFile {
|
||||
readonly title: string,
|
||||
@@ -20,7 +22,8 @@ export interface MolFile {
|
||||
readonly x: Column<number>,
|
||||
readonly y: Column<number>,
|
||||
readonly z: Column<number>,
|
||||
readonly type_symbol: Column<string>
|
||||
readonly type_symbol: Column<string>,
|
||||
readonly formal_charge: Column<number>
|
||||
},
|
||||
readonly bonds: {
|
||||
readonly count: number
|
||||
@@ -28,6 +31,57 @@ export interface MolFile {
|
||||
readonly atomIdxB: Column<number>,
|
||||
readonly order: Column<number>
|
||||
}
|
||||
readonly formalCharges: {
|
||||
readonly atomIdx: Column<number>;
|
||||
readonly charge: Column<number>;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The atom lines in a .mol file have the following structure:
|
||||
|
||||
xxxxx.xxxxyyyyy.yyyyzzzzz.zzzz aaaddcccssshhhbbbvvvHHHrrriiimmmnnneee
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Below is a breakdown of each component and its start/end indices:
|
||||
|
||||
xxxxx.xxxx (X COORDINATE, 1-10)
|
||||
yyyyy.yyyy (Y COORDINATE, 10-20)
|
||||
zzzzz.zzzz (Z COORDINATE, 20-30)
|
||||
_ (30 IS EMPTY)
|
||||
aaa (ATOM SYMBOL, 31-34)
|
||||
dd (MASS DIFF, 34-36)
|
||||
ccc (FORMAL CHARGE, 36-39)
|
||||
sss (ATOM STEREO PARITY, 39-42)
|
||||
hhh (HYDROGEN COUNT+1, 42-45)
|
||||
bbb (STEREO CARE BOX, 45-48)
|
||||
vvv (VALENCE, 48-51)
|
||||
HHH (H0 DESIGNATOR, 51-54)
|
||||
rrr (UNUSED, 54-57)
|
||||
iii (UNUSED, 57-60)
|
||||
mmm (ATOM-ATOM MAPPING NUMBER, 60-63)
|
||||
nnn (INVERSION/RETENTION FLAG, 63-66)
|
||||
eee (EXACT CHANGE FLAG, 66-69)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param key - The value found at the atom block.
|
||||
* @returns The actual formal charge based on the mapping.
|
||||
*/
|
||||
export function formalChargeMapper(key: number) {
|
||||
switch (key) {
|
||||
case 7: return -3;
|
||||
case 6: return -2;
|
||||
case 5: return -1;
|
||||
case 0: return 0;
|
||||
case 3: return 1;
|
||||
case 2: return 2;
|
||||
case 1: return 3;
|
||||
case 4: return 0;
|
||||
default:
|
||||
console.error(`Value ${key} is outside the 0-7 range, defaulting to 0.`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms'] {
|
||||
@@ -35,6 +89,7 @@ export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms
|
||||
const y = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
const z = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
const type_symbol = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
const formal_charge = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
Tokenizer.markLine(tokenizer);
|
||||
@@ -47,6 +102,8 @@ export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms
|
||||
TokenBuilder.addUnchecked(z, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
Tokenizer.trim(tokenizer, s + 31, s + 34);
|
||||
TokenBuilder.addUnchecked(type_symbol, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
Tokenizer.trim(tokenizer, s + 36, s + 39);
|
||||
TokenBuilder.addUnchecked(formal_charge, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
tokenizer.position = position;
|
||||
}
|
||||
|
||||
@@ -55,7 +112,8 @@ export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms
|
||||
x: TokenColumn(x)(Column.Schema.float),
|
||||
y: TokenColumn(y)(Column.Schema.float),
|
||||
z: TokenColumn(z)(Column.Schema.float),
|
||||
type_symbol: TokenColumn(type_symbol)(Column.Schema.str)
|
||||
type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
|
||||
formal_charge: TokenColumn(formal_charge)(Column.Schema.int)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +142,76 @@ export function handleBonds(tokenizer: Tokenizer, count: number): MolFile['bonds
|
||||
};
|
||||
}
|
||||
|
||||
interface FormalChargesRawData {
|
||||
atomIdx: Array<number>;
|
||||
charge: Array<number>;
|
||||
}
|
||||
export function handleFormalCharges(tokenizer: Tokenizer, lineStart: number, formalCharges: FormalChargesRawData) {
|
||||
|
||||
Tokenizer.trim(tokenizer, lineStart + 6, lineStart + 9);
|
||||
const numOfCharges = parseInt(Tokenizer.getTokenString(tokenizer));
|
||||
for (let i = 0; i < numOfCharges; ++i) {
|
||||
/*
|
||||
M CHG 3 1 -1 2 0 2 -1
|
||||
| | | | |
|
||||
| | | | |__charge2 (etc.)
|
||||
| | | |
|
||||
| | | |__atomIdx2
|
||||
| | |
|
||||
| | |__charge1
|
||||
| |
|
||||
| |__atomIdx1 (cursor at position 12)
|
||||
|
|
||||
|___numOfCharges
|
||||
*/
|
||||
const offset = 9 + (i * 8);
|
||||
|
||||
Tokenizer.trim(tokenizer, lineStart + offset, lineStart + offset + 4);
|
||||
const _atomIdx = Tokenizer.getTokenString(tokenizer);
|
||||
formalCharges.atomIdx.push(+_atomIdx);
|
||||
Tokenizer.trim(tokenizer, lineStart + offset + 4, lineStart + offset + 8);
|
||||
const _charge = Tokenizer.getTokenString(tokenizer);
|
||||
formalCharges.charge.push(+_charge);
|
||||
}
|
||||
/* Once the line is read, move to the next one. */
|
||||
Tokenizer.eatLine(tokenizer);
|
||||
}
|
||||
|
||||
/** Call an appropriate handler based on the property type.
|
||||
* (For now it only calls the formal charge handler, additional handlers can
|
||||
* be added for other properties.)
|
||||
*/
|
||||
export function handlePropertiesBlock(tokenizer: Tokenizer): MolFile['formalCharges'] {
|
||||
|
||||
const _atomIdx: Array<number> = [];
|
||||
const _charge: Array<number> = [];
|
||||
const _formalCharges: FormalChargesRawData = { atomIdx: _atomIdx, charge: _charge };
|
||||
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const { position: s } = tokenizer;
|
||||
|
||||
Tokenizer.trim(tokenizer, s + 3, s + 6);
|
||||
const propertyType = Tokenizer.getTokenString(tokenizer);
|
||||
|
||||
if (propertyType === 'END') break;
|
||||
Tokenizer.eatLine(tokenizer);
|
||||
|
||||
switch (propertyType) {
|
||||
case 'CHG':
|
||||
handleFormalCharges(tokenizer, s, _formalCharges);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const formalCharges: MolFile['formalCharges'] = {
|
||||
atomIdx: Column.ofIntArray(_formalCharges.atomIdx),
|
||||
charge: Column.ofIntArray(_formalCharges.charge)
|
||||
};
|
||||
return formalCharges;
|
||||
}
|
||||
|
||||
function parseInternal(data: string): Result<MolFile> {
|
||||
const tokenizer = Tokenizer(data);
|
||||
|
||||
@@ -98,12 +226,15 @@ function parseInternal(data: string): Result<MolFile> {
|
||||
const atoms = handleAtoms(tokenizer, atomCount);
|
||||
const bonds = handleBonds(tokenizer, bondCount);
|
||||
|
||||
const formalCharges = handlePropertiesBlock(tokenizer);
|
||||
|
||||
const result: MolFile = {
|
||||
title,
|
||||
program,
|
||||
comment,
|
||||
atoms,
|
||||
bonds
|
||||
bonds,
|
||||
formalCharges,
|
||||
};
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Zepei Xu <xuzepei19950617@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -259,6 +259,36 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function handleCrysin(state: State) {
|
||||
const { tokenizer } = state;
|
||||
|
||||
while (tokenizer.position < tokenizer.data.length) {
|
||||
const l = getTokenString(tokenizer);
|
||||
if (l === '@<TRIPOS>MOLECULE') {
|
||||
return;
|
||||
} else if (l === '@<TRIPOS>CRYSIN') {
|
||||
break;
|
||||
} else {
|
||||
markLine(tokenizer);
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenizer.position >= tokenizer.data.length) return;
|
||||
|
||||
markLine(tokenizer);
|
||||
const values = getTokenString(tokenizer).trim().split(reWhitespace);
|
||||
return {
|
||||
a: parseFloat(values[0]),
|
||||
b: parseFloat(values[1]),
|
||||
c: parseFloat(values[2]),
|
||||
alpha: parseFloat(values[3]),
|
||||
beta: parseFloat(values[4]),
|
||||
gamma: parseFloat(values[5]),
|
||||
spaceGroup: parseInt(values[6], 10),
|
||||
setting: parseInt(values[7], 10),
|
||||
};
|
||||
}
|
||||
|
||||
async function parseInternal(ctx: RuntimeContext, data: string, name: string): Promise<Result<Schema.Mol2File>> {
|
||||
const tokenizer = Tokenizer(data);
|
||||
|
||||
@@ -269,7 +299,8 @@ async function parseInternal(ctx: RuntimeContext, data: string, name: string): P
|
||||
handleMolecule(state);
|
||||
const atoms = await handleAtoms(state);
|
||||
const bonds = await handleBonds(state);
|
||||
structures.push({ molecule: state.molecule, atoms, bonds });
|
||||
const crysin = handleCrysin(state);
|
||||
structures.push({ molecule: state.molecule, atoms, bonds, crysin });
|
||||
skipWhitespace(tokenizer);
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>MOLECULE' && tokenizer.position < tokenizer.data.length) {
|
||||
markLine(tokenizer);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,6 +11,7 @@ import { Column } from '../../../mol-data/db';
|
||||
// @<TRIPOS>MOLECULE
|
||||
// @<TRIPOS>ATOM
|
||||
// @<TRIPOS>BOND
|
||||
// @<TRIPOS>CRYSIN
|
||||
//
|
||||
// note that the format is not a fixed column format but white space separated
|
||||
|
||||
@@ -56,10 +57,22 @@ export interface Mol2Bonds {
|
||||
status_bits: Column<string>
|
||||
}
|
||||
|
||||
export interface Mol2Crysin {
|
||||
a: number
|
||||
b: number
|
||||
c: number
|
||||
alpha: number
|
||||
beta: number
|
||||
gamma: number
|
||||
spaceGroup: number
|
||||
setting: number
|
||||
}
|
||||
|
||||
export interface Mol2Structure {
|
||||
molecule: Readonly<Mol2Molecule>,
|
||||
atoms: Readonly<Mol2Atoms>,
|
||||
bonds: Readonly<Mol2Bonds>
|
||||
crysin?: Readonly<Mol2Crysin>
|
||||
}
|
||||
|
||||
export interface Mol2File {
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Jason Pattle <jpattle@exscientia.co.uk>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { MolFile } from '../mol/parser';
|
||||
import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
|
||||
@@ -61,6 +68,9 @@ export function handleAtomsV3(
|
||||
y: TokenColumn(y)(Column.Schema.float),
|
||||
z: TokenColumn(z)(Column.Schema.float),
|
||||
type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
|
||||
/* No support for formal charge parsing in V3000 molfiles at the moment,
|
||||
so all charges default to 0.*/
|
||||
formal_charge: Column.ofConst(0, atomCount, Column.Schema.int)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Jason Pattle <jpattle@exscientia.co.uk>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { MolFile, handleAtoms, handleBonds } from '../mol/parser';
|
||||
import { MolFile, handleAtoms, handleBonds, handlePropertiesBlock } from '../mol/parser';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
|
||||
@@ -29,6 +31,7 @@ export interface SdfFile {
|
||||
|
||||
|
||||
const delimiter = '$$$$';
|
||||
|
||||
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
|
||||
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
|
||||
const data = TokenBuilder.create(tokenizer.data, 32);
|
||||
@@ -93,12 +96,20 @@ function handleMolFile(tokenizer: Tokenizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* No support for formal charge parsing in V3000 molfiles at the moment,
|
||||
so all charges default to 0.*/
|
||||
const nullFormalCharges: MolFile['formalCharges'] = {
|
||||
atomIdx: Column.ofConst(0, atomCount, Column.Schema.int),
|
||||
charge: Column.ofConst(0, atomCount, Column.Schema.int)
|
||||
};
|
||||
|
||||
const atoms = molIsV3 ? handleAtomsV3(tokenizer, atomCount) : handleAtoms(tokenizer, atomCount);
|
||||
const bonds = molIsV3 ? handleBondsV3(tokenizer, bondCount) : handleBonds(tokenizer, bondCount);
|
||||
const formalCharges = molIsV3 ? nullFormalCharges : handlePropertiesBlock(tokenizer);
|
||||
const dataItems = handleDataItems(tokenizer);
|
||||
|
||||
return {
|
||||
molFile: { title, program, comment, atoms, bonds },
|
||||
molFile: { title, program, comment, atoms, bonds, formalCharges },
|
||||
dataItems
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,8 +191,14 @@ function getFieldData(field: Field<any, any>, arrayCtor: ArrayCtor<string | numb
|
||||
array[offset] = '';
|
||||
allPresent = false;
|
||||
} else {
|
||||
mask[offset] = Column.ValueKind.Present;
|
||||
array[offset] = getter(key, d, offset);
|
||||
const value = getter(key, d, offset);
|
||||
if (typeof value === 'string' && !value) {
|
||||
mask[offset] = Column.ValueKind.NotPresent;
|
||||
allPresent = false;
|
||||
} else {
|
||||
mask[offset] = Column.ValueKind.Present;
|
||||
}
|
||||
array[offset] = value;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ValueCell } from '../../../mol-util';
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
|
||||
import { decodeFloatRGB } from '../../../mol-util/float-packing';
|
||||
import { unpackRGBToInt } from '../../../mol-util/number-packing';
|
||||
import { ShaderCode } from '../../../mol-gl/shader-code';
|
||||
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
|
||||
import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values } from '../../../mol-gl/renderable/schema';
|
||||
@@ -462,7 +462,7 @@ function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texD
|
||||
for (let ix = 0; ix < dx; ++ix) {
|
||||
const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix);
|
||||
data[j] = image[idx + 3] / 255;
|
||||
idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2]);
|
||||
idData[j] = unpackRGBToInt(image[idx], image[idx + 1], image[idx + 2]);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -11,6 +11,7 @@ import { OrderedSet } from '../../../mol-data/int';
|
||||
import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
|
||||
import { Box3D } from './box3d';
|
||||
import { Axes3D } from './axes3d';
|
||||
import { PrincipalAxes } from '../../linear-algebra/matrix/principal-axes';
|
||||
|
||||
interface Sphere3D {
|
||||
center: Vec3,
|
||||
@@ -202,11 +203,28 @@ namespace Sphere3D {
|
||||
return out;
|
||||
}
|
||||
if (hasExtrema(sphere)) {
|
||||
const positions = new Float32Array(sphere.extrema.length * 3);
|
||||
for (let i = 0; i < sphere.extrema.length; i++) {
|
||||
Vec3.toArray(sphere.extrema[i], positions, i * 3);
|
||||
}
|
||||
|
||||
const axes = PrincipalAxes.calculateMomentsAxes(positions);
|
||||
Axes3D.scale(axes, Axes3D.normalize(axes, axes), delta);
|
||||
|
||||
setExtrema(out, sphere.extrema.map(e => {
|
||||
Vec3.sub(tmpDir, e, sphere.center);
|
||||
const dist = Vec3.distance(sphere.center, e);
|
||||
Vec3.normalize(tmpDir, tmpDir);
|
||||
return Vec3.scaleAndAdd(Vec3(), sphere.center, tmpDir, dist + delta);
|
||||
const out = Vec3.clone(e);
|
||||
|
||||
const sA = Vec3.dot(tmpDir, axes.dirA) < 0 ? -1 : 1;
|
||||
Vec3.scaleAndAdd(out, out, axes.dirA, sA);
|
||||
|
||||
const sB = Vec3.dot(tmpDir, axes.dirB) < 0 ? -1 : 1;
|
||||
Vec3.scaleAndAdd(out, out, axes.dirB, sB);
|
||||
|
||||
const sC = Vec3.dot(tmpDir, axes.dirC) < 0 ? -1 : 1;
|
||||
Vec3.scaleAndAdd(out, out, axes.dirC, sC);
|
||||
|
||||
return out;
|
||||
}));
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -52,24 +52,30 @@ namespace SymmetryOperator {
|
||||
export const RotationTranslationEpsilon = 0.005;
|
||||
|
||||
export type CreateInfo = { assembly?: SymmetryOperator['assembly'], ncsId?: number, hkl?: Vec3, spgrOp?: number }
|
||||
export function create(name: string, matrix: Mat4, info?: CreateInfo): SymmetryOperator {
|
||||
export function create(name: string, matrix: Mat4, info?: CreateInfo | SymmetryOperator): SymmetryOperator {
|
||||
let { assembly, ncsId, hkl, spgrOp } = info || { };
|
||||
const _hkl = hkl ? Vec3.clone(hkl) : Vec3();
|
||||
spgrOp = defaults(spgrOp, -1);
|
||||
ncsId = ncsId || -1;
|
||||
const suffix = getSuffix(info);
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
const isIdentity = Mat4.isIdentity(matrix);
|
||||
const suffix = getSuffix(info, isIdentity);
|
||||
if (isIdentity) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) {
|
||||
console.warn(`Symmetry operator (${name}) should be a composition of rotation and translation.`);
|
||||
}
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
}
|
||||
|
||||
function getSuffix(info?: CreateInfo) {
|
||||
function isSymmetryOperator(x: any): x is SymmetryOperator {
|
||||
return !!x && !!x.matrix && !!x.inverse && typeof x.name === 'string';
|
||||
}
|
||||
|
||||
function getSuffix(info?: CreateInfo | SymmetryOperator, isIdentity?: boolean) {
|
||||
if (!info) return '';
|
||||
|
||||
if (info.assembly) {
|
||||
return `_${info.assembly.operId}`;
|
||||
if (isSymmetryOperator(info)) return info.suffix;
|
||||
return isIdentity ? '' : `_${info.assembly.operId}`;
|
||||
}
|
||||
|
||||
if (typeof info.spgrOp !== 'undefined' && typeof info.hkl !== 'undefined' && info.spgrOp !== -1) {
|
||||
|
||||
@@ -7,21 +7,21 @@
|
||||
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { Interval, Segmentation } from '../../../mol-data/int';
|
||||
import { UUID } from '../../../mol-util/uuid';
|
||||
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
|
||||
import { toDatabase } from '../../../mol-io/reader/cif/schema';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ChainIndex, ElementIndex } from '../../../mol-model/structure';
|
||||
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
|
||||
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
|
||||
import { ElementSymbol } from '../../../mol-model/structure/model/types';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
|
||||
import { AtomSite } from './schema';
|
||||
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
|
||||
import { ElementSymbol } from '../../../mol-model/structure/model/types';
|
||||
import { UUID } from '../../../mol-util/uuid';
|
||||
import { ModelFormat } from '../../format';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { MmcifFormat } from '../mmcif';
|
||||
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
|
||||
import { toDatabase } from '../../../mol-io/reader/cif/schema';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { AtomSite } from './schema';
|
||||
|
||||
function findHierarchyOffsets(atom_site: AtomSite) {
|
||||
if (atom_site._rowCount === 0) return { residues: [], chains: [] };
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface CoarseData {
|
||||
|
||||
export const EmptyCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any };
|
||||
|
||||
export function getCoarse(data: CoarseData, properties: Model['properties']): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
|
||||
export function getCoarse(data: CoarseData, chemicalComponentMap: Model['properties']['chemicalComponentMap']): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
|
||||
const { ihm_sphere_obj_site, ihm_gaussian_obj_site } = data;
|
||||
|
||||
if (ihm_sphere_obj_site._rowCount === 0 && ihm_gaussian_obj_site._rowCount === 0) return EmptyCoarse;
|
||||
@@ -38,12 +38,12 @@ export function getCoarse(data: CoarseData, properties: Model['properties']): {
|
||||
const sphereData = getData(ihm_sphere_obj_site);
|
||||
const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
|
||||
const sphereKeys = getCoarseKeys(sphereData, data.entities);
|
||||
const sphereRanges = getCoarseRanges(sphereData, properties.chemicalComponentMap);
|
||||
const sphereRanges = getCoarseRanges(sphereData, chemicalComponentMap);
|
||||
|
||||
const gaussianData = getData(ihm_gaussian_obj_site);
|
||||
const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site);
|
||||
const gaussianKeys = getCoarseKeys(gaussianData, data.entities);
|
||||
const gaussianRanges = getCoarseRanges(gaussianData, properties.chemicalComponentMap);
|
||||
const gaussianRanges = getCoarseRanges(gaussianData, chemicalComponentMap);
|
||||
|
||||
return {
|
||||
hierarchy: {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getEntityType, getEntitySubtype } from '../../../mol-model/structure/mo
|
||||
import { ElementIndex, EntityIndex, Model } from '../../../mol-model/structure/model';
|
||||
import { BasicData, BasicSchema, Entity } from './schema';
|
||||
|
||||
export function getEntities(data: BasicData, properties: Model['properties']): Entities {
|
||||
export function getEntityData(data: BasicData): Entities {
|
||||
let entityData: Entity;
|
||||
|
||||
if (!data.entity.id.isDefined) {
|
||||
@@ -121,28 +121,32 @@ export function getEntities(data: BasicData, properties: Model['properties']): E
|
||||
|
||||
const subtypeColumn = Column.ofArray({ array: subtypes, schema: EntitySubtype });
|
||||
|
||||
//
|
||||
|
||||
const prdIds: string[] = new Array(entityData._rowCount);
|
||||
prdIds.fill('');
|
||||
|
||||
if (data.pdbx_molecule && data.pdbx_molecule.prd_id.isDefined) {
|
||||
const { asym_id, prd_id, _rowCount } = data.pdbx_molecule;
|
||||
for (let i = 0; i < _rowCount; ++i) {
|
||||
const asymId = asym_id.value(i);
|
||||
const entityId = properties.structAsymMap.get(asymId)?.entity_id;
|
||||
if (entityId !== undefined) {
|
||||
prdIds[getEntityIndex(entityId)] = prd_id.value(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prdIdColumn = Column.ofArray({ array: prdIds, schema: Column.Schema.str });
|
||||
|
||||
return {
|
||||
data: entityData,
|
||||
subtype: subtypeColumn,
|
||||
prd_id: prdIdColumn,
|
||||
getEntityIndex
|
||||
};
|
||||
}
|
||||
|
||||
export function getEntitiesWithPRD(data: BasicData, entities: Entities, structAsymMap: Model['properties']['structAsymMap']): Entities {
|
||||
if (!data.pdbx_molecule || !data.pdbx_molecule.prd_id.isDefined) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
const prdIds: string[] = new Array(entities.data._rowCount);
|
||||
prdIds.fill('');
|
||||
const { asym_id, prd_id, _rowCount } = data.pdbx_molecule;
|
||||
for (let i = 0; i < _rowCount; ++i) {
|
||||
const asymId = asym_id.value(i);
|
||||
const entityId = structAsymMap.get(asymId)?.entity_id;
|
||||
if (entityId !== undefined) {
|
||||
prdIds[entities.getEntityIndex(entityId)] = prd_id.value(i);
|
||||
}
|
||||
}
|
||||
const prdIdColumn = Column.ofArray({ array: prdIds, schema: Column.Schema.str });
|
||||
|
||||
return {
|
||||
...entities,
|
||||
prd_id: prdIdColumn
|
||||
};
|
||||
}
|
||||
@@ -18,13 +18,13 @@ import { sortAtomSite } from './sort';
|
||||
import { ModelFormat } from '../../format';
|
||||
import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
|
||||
import { AtomSite, BasicData } from './schema';
|
||||
import { getProperties } from './properties';
|
||||
import { getEntities } from './entities';
|
||||
import { getChemicalComponentMap, getMissingResidues, getSaccharideComponentMap, getStructAsymMap } from './properties';
|
||||
import { getEntitiesWithPRD, getEntityData } from './entities';
|
||||
import { getModelGroupName } from './util';
|
||||
import { ArrayTrajectory } from '../../../mol-model/structure/trajectory';
|
||||
|
||||
export async function createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
|
||||
const properties = getProperties(data);
|
||||
const properties = getCommonProperties(data, format);
|
||||
const models = data.ihm_model_list._rowCount > 0
|
||||
? await readIntegrative(ctx, data, properties, format)
|
||||
: await readStandard(ctx, data, properties, format);
|
||||
@@ -36,9 +36,18 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
|
||||
return new ArrayTrajectory(models);
|
||||
}
|
||||
|
||||
/** Standard atomic model */
|
||||
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
|
||||
type CommonProperties = Omit<Model['properties'], 'structAsymMap'>
|
||||
|
||||
function getCommonProperties(data: BasicData, format: ModelFormat): CommonProperties {
|
||||
return {
|
||||
missingResidues: getMissingResidues(data),
|
||||
chemicalComponentMap: getChemicalComponentMap(data),
|
||||
saccharideComponentMap: getSaccharideComponentMap(data)
|
||||
};
|
||||
}
|
||||
|
||||
/** Standard atomic model */
|
||||
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: CommonProperties, format: ModelFormat, previous?: Model): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
|
||||
const modelNum = atom_site.pdbx_PDB_model_num.value(0);
|
||||
if (previous && atomic.sameAsPrevious) {
|
||||
@@ -54,6 +63,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
const coarse = EmptyCoarse;
|
||||
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy);
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence);
|
||||
const structAsymMap = getStructAsymMap(atomic.hierarchy);
|
||||
|
||||
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
? data.entry.id.value(0) : format.name;
|
||||
@@ -70,7 +80,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
sourceData: format,
|
||||
modelNum,
|
||||
parent: undefined,
|
||||
entities,
|
||||
entities: getEntitiesWithPRD(data, entities, structAsymMap),
|
||||
sequence,
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
@@ -78,7 +88,10 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
properties: {
|
||||
...properties,
|
||||
structAsymMap
|
||||
},
|
||||
customProperties: new CustomProperties(),
|
||||
_staticPropertyData: Object.create(null),
|
||||
_dynamicPropertyData: Object.create(null)
|
||||
@@ -86,9 +99,9 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
}
|
||||
|
||||
/** Integrative model with atomic/coarse parts */
|
||||
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
|
||||
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: CommonProperties, format: ModelFormat): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
|
||||
const coarse = getCoarse(ihm, properties);
|
||||
const coarse = getCoarse(ihm, properties.chemicalComponentMap);
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy);
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence);
|
||||
|
||||
@@ -101,6 +114,8 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
if (ihm.model_name) label.push(ihm.model_name);
|
||||
if (ihm.model_group_name) label.push(ihm.model_group_name);
|
||||
|
||||
const structAsymMap = getStructAsymMap(atomic.hierarchy, data);
|
||||
|
||||
return {
|
||||
id: UUID.create22(),
|
||||
entryId: entry,
|
||||
@@ -109,7 +124,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
sourceData: format,
|
||||
modelNum: ihm.model_id,
|
||||
parent: undefined,
|
||||
entities: ihm.entities,
|
||||
entities: getEntitiesWithPRD(data, ihm.entities, structAsymMap),
|
||||
sequence,
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
@@ -117,7 +132,10 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
properties: {
|
||||
...properties,
|
||||
structAsymMap
|
||||
},
|
||||
customProperties: new CustomProperties(),
|
||||
_staticPropertyData: Object.create(null),
|
||||
_dynamicPropertyData: Object.create(null)
|
||||
@@ -132,12 +150,12 @@ function findModelEnd(num: Column<number>, startIndex: number) {
|
||||
return endIndex;
|
||||
}
|
||||
|
||||
async function readStandard(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
|
||||
async function readStandard(ctx: RuntimeContext, data: BasicData, properties: CommonProperties, format: ModelFormat) {
|
||||
const models: Model[] = [];
|
||||
|
||||
if (data.atom_site) {
|
||||
const atomCount = data.atom_site.id.rowCount;
|
||||
const entities = getEntities(data, properties);
|
||||
const entities = getEntityData(data);
|
||||
|
||||
let modelStart = 0;
|
||||
while (modelStart < atomCount) {
|
||||
@@ -170,8 +188,8 @@ function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
|
||||
|
||||
|
||||
|
||||
async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
|
||||
const entities = getEntities(data, properties);
|
||||
async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: CommonProperties, format: ModelFormat) {
|
||||
const entities = getEntityData(data);
|
||||
// when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num`
|
||||
const atom_sites_modelColumn = data.atom_site.ihm_model_id.isDefined
|
||||
? data.atom_site.ihm_model_id : data.atom_site.pdbx_PDB_model_num;
|
||||
@@ -206,7 +224,7 @@ async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties:
|
||||
model_id: id,
|
||||
model_name: model_name.value(i),
|
||||
model_group_name: getModelGroupName(id, data),
|
||||
entities: entities,
|
||||
entities,
|
||||
atom_site,
|
||||
atom_site_sourceIndex,
|
||||
ihm_sphere_obj_site: sphere_sites.has(id) ? sphere_sites.get(id)!.table : Table.window(data.ihm_sphere_obj_site, data.ihm_sphere_obj_site._schema, 0, 0),
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Table } from '../../../mol-data/db';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
|
||||
import { ChemicalComponent, MissingResidue, StructAsym } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getMoleculeType, MoleculeType, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
|
||||
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { getDefaultChemicalComponent, getMoleculeType, MoleculeType } from '../../../mol-model/structure/model/types';
|
||||
import { SaccharideCompIdMap, SaccharideComponent, SaccharideComponentMap, SaccharidesSnfgMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { memoize1 } from '../../../mol-util/memoize';
|
||||
import { BasicData } from './schema';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
|
||||
function getMissingResidues(data: BasicData): Model['properties']['missingResidues'] {
|
||||
export function getMissingResidues(data: BasicData): Model['properties']['missingResidues'] {
|
||||
const map = new Map<string, MissingResidue>();
|
||||
const getKey = (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return `${model_num}|${asym_id}|${seq_id}`;
|
||||
@@ -36,7 +37,7 @@ function getMissingResidues(data: BasicData): Model['properties']['missingResidu
|
||||
};
|
||||
}
|
||||
|
||||
function getChemicalComponentMap(data: BasicData): Model['properties']['chemicalComponentMap'] {
|
||||
export function getChemicalComponentMap(data: BasicData): Model['properties']['chemicalComponentMap'] {
|
||||
const map = new Map<string, ChemicalComponent>();
|
||||
|
||||
if (data.chem_comp._rowCount > 0) {
|
||||
@@ -53,7 +54,7 @@ function getChemicalComponentMap(data: BasicData): Model['properties']['chemical
|
||||
return map;
|
||||
}
|
||||
|
||||
function getSaccharideComponentMap(data: BasicData): SaccharideComponentMap {
|
||||
export function getSaccharideComponentMap(data: BasicData): SaccharideComponentMap {
|
||||
const map = new Map<string, SaccharideComponent>();
|
||||
|
||||
if (data.pdbx_chem_comp_identifier._rowCount > 0) {
|
||||
@@ -108,22 +109,18 @@ const getUniqueComponentNames = memoize1((data: BasicData) => {
|
||||
});
|
||||
|
||||
|
||||
function getStructAsymMap(data: BasicData): Model['properties']['structAsymMap'] {
|
||||
export function getStructAsymMap(atomic: AtomicHierarchy, data?: BasicData): Model['properties']['structAsymMap'] {
|
||||
const map = new Map<string, StructAsym>();
|
||||
|
||||
const { label_asym_id, auth_asym_id, label_entity_id } = data.atom_site;
|
||||
for (let i = 0, il = label_asym_id.rowCount; i < il; ++i) {
|
||||
const { auth_asym_id, label_asym_id, label_entity_id } = atomic.chains;
|
||||
|
||||
for (let i = 0, _i = atomic.chains._rowCount; i < _i; i ++) {
|
||||
const id = label_asym_id.value(i);
|
||||
if (!map.has(id)) {
|
||||
map.set(id, {
|
||||
id,
|
||||
auth_id: auth_asym_id.value(i),
|
||||
entity_id: label_entity_id.value(i)
|
||||
});
|
||||
}
|
||||
map.set(id, { id, auth_id: auth_asym_id.value(i), entity_id: label_entity_id.value(i) });
|
||||
}
|
||||
|
||||
if (data.struct_asym._rowCount > 0) {
|
||||
// to get asym mapping for coarse/ihm data
|
||||
if (data?.struct_asym._rowCount) {
|
||||
const { id, entity_id } = data.struct_asym;
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
const _id = id.value(i);
|
||||
@@ -136,14 +133,6 @@ function getStructAsymMap(data: BasicData): Model['properties']['structAsymMap']
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getProperties(data: BasicData): Model['properties'] {
|
||||
return {
|
||||
missingResidues: getMissingResidues(data),
|
||||
chemicalComponentMap: getChemicalComponentMap(data),
|
||||
saccharideComponentMap: getSaccharideComponentMap(data),
|
||||
structAsymMap: getStructAsymMap(data)
|
||||
};
|
||||
return map;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -30,7 +30,7 @@ const DnaAtomIdsList = [
|
||||
|
||||
/** Used to reduce false positives for atom name-based type guessing */
|
||||
const NonPolymerNames = new Set([
|
||||
'FMN', 'NCN', 'FNS', 'FMA' // Mononucleotides
|
||||
'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP' // Mononucleotides
|
||||
]);
|
||||
|
||||
const StandardComponents = (function () {
|
||||
@@ -156,7 +156,7 @@ export class ComponentBuilder {
|
||||
this.set(StandardComponents.get(compId)!);
|
||||
} else if (WaterNames.has(compId)) {
|
||||
this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
|
||||
} else if (NonPolymerNames.has(compId)) {
|
||||
} else if (NonPolymerNames.has(compId.toUpperCase())) {
|
||||
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'non-polymer' });
|
||||
} else {
|
||||
const atomIds = this.getAtomIds(index);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Model } from '../../../mol-model/structure';
|
||||
@@ -44,7 +45,7 @@ interface FormatPropertyProvider<T> {
|
||||
}
|
||||
|
||||
namespace FormatPropertyProvider {
|
||||
export function create<T>(descriptor: CustomPropertyDescriptor): FormatPropertyProvider<T> {
|
||||
export function create<T>(descriptor: CustomPropertyDescriptor, options?: { asDynamic?: boolean }): FormatPropertyProvider<T> {
|
||||
const { name } = descriptor;
|
||||
const formatRegistry = new FormatRegistry<T>();
|
||||
|
||||
@@ -55,21 +56,31 @@ namespace FormatPropertyProvider {
|
||||
return formatRegistry.isApplicable(model);
|
||||
},
|
||||
get(model: Model): T | undefined {
|
||||
if (model._staticPropertyData[name]) return model._staticPropertyData[name];
|
||||
const store = options?.asDynamic ? model._dynamicPropertyData : model._staticPropertyData;
|
||||
|
||||
if (store[name]) return store[name];
|
||||
if (model.customProperties.has(descriptor)) return;
|
||||
|
||||
const obtain = formatRegistry.get(model.sourceData.kind);
|
||||
if (!obtain) return;
|
||||
|
||||
model._staticPropertyData[name] = obtain(model);
|
||||
store[name] = obtain(model);
|
||||
model.customProperties.add(descriptor);
|
||||
return model._staticPropertyData[name];
|
||||
return store[name];
|
||||
},
|
||||
set(model: Model, value: T) {
|
||||
model._staticPropertyData[name] = value;
|
||||
if (options?.asDynamic) {
|
||||
model._dynamicPropertyData[name] = value;
|
||||
} else {
|
||||
model._staticPropertyData[name] = value;
|
||||
}
|
||||
},
|
||||
delete(model: Model) {
|
||||
delete model._staticPropertyData[name];
|
||||
if (options?.asDynamic) {
|
||||
delete model._dynamicPropertyData[name];
|
||||
} else {
|
||||
delete model._staticPropertyData[name];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { MolFile } from '../../mol-io/reader/mol/parser';
|
||||
import { MolFile, formalChargeMapper } from '../../mol-io/reader/mol/parser';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { createModels } from './basic/parser';
|
||||
@@ -18,13 +19,24 @@ import { IndexPairBonds } from './property/bonds/index-pair';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
|
||||
export async function getMolModels(mol: MolFile, format: ModelFormat<any> | undefined, ctx: RuntimeContext) {
|
||||
const { atoms, bonds } = mol;
|
||||
const { atoms, bonds, formalCharges } = mol;
|
||||
|
||||
const MOL = Column.ofConst('MOL', mol.atoms.count, Column.Schema.str);
|
||||
const A = Column.ofConst('A', mol.atoms.count, Column.Schema.str);
|
||||
const type_symbol = Column.asArrayColumn(atoms.type_symbol);
|
||||
const seq_id = Column.ofConst(1, atoms.count, Column.Schema.int);
|
||||
|
||||
const computedFormalCharges = new Int32Array(mol.atoms.count);
|
||||
if (formalCharges.atomIdx.rowCount > 0) {
|
||||
for (let i = 0; i < formalCharges.atomIdx.rowCount; i++) {
|
||||
computedFormalCharges[formalCharges.atomIdx.value(i) - 1] = formalCharges.charge.value(i);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < mol.atoms.count; i++) {
|
||||
computedFormalCharges[i] = formalChargeMapper(atoms.formal_charge.value(i));
|
||||
}
|
||||
}
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: A,
|
||||
auth_atom_id: type_symbol,
|
||||
@@ -45,6 +57,7 @@ export async function getMolModels(mol: MolFile, format: ModelFormat<any> | unde
|
||||
type_symbol,
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
|
||||
pdbx_formal_charge: Column.ofIntArray(computedFormalCharges)
|
||||
}, atoms.count);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
import { Model, Symmetry } from '../../mol-model/structure/model';
|
||||
import { BondType, MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { createModels } from './basic/parser';
|
||||
@@ -14,22 +14,34 @@ import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { ModelFormat } from '../format';
|
||||
import { IndexPairBonds } from './property/bonds/index-pair';
|
||||
import { Mol2File } from '../../mol-io/reader/mol2/schema';
|
||||
import { Mol2Crysin, Mol2File } from '../../mol-io/reader/mol2/schema';
|
||||
import { AtomPartialCharge } from './property/partial-charge';
|
||||
import { Trajectory, ArrayTrajectory } from '../../mol-model/structure';
|
||||
import { guessElementSymbolString } from './util';
|
||||
import { ModelSymmetry } from './property/symmetry';
|
||||
import { Spacegroup, SpacegroupCell } from '../../mol-math/geometry';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
|
||||
async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
const models: Model[] = [];
|
||||
|
||||
for (let i = 0, il = mol2.structures.length; i < il; ++i) {
|
||||
const { atoms, bonds, molecule } = mol2.structures[i];
|
||||
const { molecule, atoms, bonds, crysin } = mol2.structures[i];
|
||||
|
||||
const A = Column.ofConst('A', atoms.count, Column.Schema.str);
|
||||
|
||||
const type_symbol = new Array<string>(atoms.count);
|
||||
let hasAtomType = false;
|
||||
for (let i = 0; i < atoms.count; ++i) {
|
||||
type_symbol[i] = guessElementSymbolString(atoms.atom_name.value(i));
|
||||
if (atoms.atom_type.value(i).includes('.')) {
|
||||
hasAtomType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < atoms.count; ++i) {
|
||||
type_symbol[i] = hasAtomType
|
||||
? atoms.atom_type.value(i).split('.')[0].toUpperCase()
|
||||
: guessElementSymbolString(atoms.atom_name.value(i));
|
||||
}
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
@@ -74,6 +86,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
if (_models.frameCount > 0) {
|
||||
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
|
||||
const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
|
||||
const key = bonds.bond_id;
|
||||
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
|
||||
switch (x) {
|
||||
case 'ar': // aromatic
|
||||
@@ -90,17 +103,17 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
const flag = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
|
||||
switch (x) {
|
||||
case 'ar': // aromatic
|
||||
case 'am': // amide
|
||||
return BondType.Flag.Aromatic | BondType.Flag.Covalent;
|
||||
case 'du': // dummy
|
||||
case 'nc': // not connected
|
||||
return BondType.Flag.None;
|
||||
case 'am': // amide
|
||||
case 'un': // unknown
|
||||
default:
|
||||
return BondType.Flag.Covalent;
|
||||
}
|
||||
}, Int8Array));
|
||||
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order, flag }, count: atoms.count });
|
||||
const pairBonds = IndexPairBonds.fromData({ pairs: { key, indexA, indexB, order, flag }, count: atoms.count });
|
||||
|
||||
const first = _models.representative;
|
||||
IndexPairBonds.Provider.set(first, pairBonds);
|
||||
@@ -110,6 +123,11 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
type: molecule.charge_type
|
||||
});
|
||||
|
||||
if (crysin) {
|
||||
const symmetry = getSymmetry(crysin);
|
||||
if (symmetry) ModelSymmetry.Provider.set(first, symmetry);
|
||||
}
|
||||
|
||||
models.push(first);
|
||||
}
|
||||
}
|
||||
@@ -117,6 +135,24 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
return new ArrayTrajectory(models);
|
||||
}
|
||||
|
||||
function getSymmetry(crysin: Mol2Crysin): Symmetry | undefined {
|
||||
// TODO handle `crysin.setting`
|
||||
if (crysin.setting !== 1) return;
|
||||
|
||||
const spaceCell = SpacegroupCell.create(
|
||||
crysin.spaceGroup,
|
||||
Vec3.create(crysin.a, crysin.b, crysin.c),
|
||||
Vec3.scale(Vec3(), Vec3.create(crysin.alpha, crysin.beta, crysin.gamma), Math.PI / 180)
|
||||
);
|
||||
|
||||
return {
|
||||
spacegroup: Spacegroup.create(spaceCell),
|
||||
assemblies: [],
|
||||
isNonStandardCrystalFrame: false,
|
||||
ncsOperators: []
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { Mol2Format };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -56,10 +56,10 @@ export namespace ComponentBond {
|
||||
const entries: Map<string, Entry> = new Map();
|
||||
|
||||
function addEntry(id: string) {
|
||||
// weird behavior when 'PRO' is requested - will report a single bond between N and H because a later operation would override real content
|
||||
if (entries.has(id)) {
|
||||
return entries.get(id)!;
|
||||
}
|
||||
// weird behavior when 'PRO' is requested - will report a single bond
|
||||
// between N and H because a later operation would override real content
|
||||
if (entries.has(id)) return entries.get(id)!;
|
||||
|
||||
const e = new Entry(id);
|
||||
entries.set(id, e);
|
||||
return e;
|
||||
@@ -83,10 +83,8 @@ export namespace ComponentBond {
|
||||
let ord = 1;
|
||||
if (aromatic) flags |= BondType.Flag.Aromatic;
|
||||
switch (order.toLowerCase()) {
|
||||
case 'doub':
|
||||
case 'delo':
|
||||
ord = 2;
|
||||
break;
|
||||
case 'delo': flags |= BondType.Flag.Aromatic; break;
|
||||
case 'doub': ord = 2; break;
|
||||
case 'trip': ord = 3; break;
|
||||
case 'quad': ord = 4; break;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
|
||||
@@ -10,9 +11,9 @@ import { Column } from '../../../../mol-data/db';
|
||||
import { FormatPropertyProvider } from '../../common/property';
|
||||
import { BondType } from '../../../../mol-model/structure/model/types';
|
||||
import { ElementIndex } from '../../../../mol-model/structure';
|
||||
import { DefaultBondMaxRadius } from '../../../../mol-model/structure/structure/unit/bonds/common';
|
||||
|
||||
export type IndexPairsProps = {
|
||||
readonly key: ArrayLike<number>
|
||||
readonly order: ArrayLike<number>
|
||||
readonly distance: ArrayLike<number>
|
||||
readonly flag: ArrayLike<BondType.Flag>
|
||||
@@ -22,17 +23,19 @@ export type IndexPairBonds = { bonds: IndexPairs, maxDistance: number }
|
||||
|
||||
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairsProps>, count: number): IndexPairs {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB);
|
||||
const key = new Int32Array(builder.slotCount);
|
||||
const order = new Int8Array(builder.slotCount);
|
||||
const distance = new Array(builder.slotCount);
|
||||
const flag = new Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(key, props.key ? props.key[i] : -1);
|
||||
builder.assignProperty(order, props.order ? props.order[i] : 1);
|
||||
builder.assignProperty(distance, props.distance ? props.distance[i] : -1);
|
||||
builder.assignProperty(flag, props.flag ? props.flag[i] : BondType.Flag.Covalent);
|
||||
}
|
||||
|
||||
return builder.createGraph({ order, distance, flag });
|
||||
return builder.createGraph({ key, order, distance, flag });
|
||||
}
|
||||
|
||||
export namespace IndexPairBonds {
|
||||
@@ -40,20 +43,38 @@ export namespace IndexPairBonds {
|
||||
name: 'index_pair_bonds',
|
||||
};
|
||||
|
||||
export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor);
|
||||
export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor, { asDynamic: true });
|
||||
|
||||
export type Data = {
|
||||
pairs: {
|
||||
indexA: Column<number>,
|
||||
indexB: Column<number>
|
||||
indexB: Column<number>,
|
||||
key?: Column<number>,
|
||||
order?: Column<number>,
|
||||
/**
|
||||
* Useful for bonds in periodic cells. That is, only bonds within the given
|
||||
* distance are added. This allows for bond between periodic image but
|
||||
* avoids unwanted bonds with wrong distances. If negative, test using the
|
||||
* `maxDistance` option from `Props`.
|
||||
*/
|
||||
distance?: Column<number>,
|
||||
flag?: Column<BondType.Flag>,
|
||||
},
|
||||
count: number
|
||||
}
|
||||
|
||||
export const DefaultProps = { maxDistance: DefaultBondMaxRadius };
|
||||
export const DefaultProps = {
|
||||
/**
|
||||
* If negative, test using element-based threshold, otherwise distance in Angstrom.
|
||||
*
|
||||
* This option exists to handle bonds in periodic cells. For systems that are
|
||||
* made from beads (as opposed to atomic elements), set to a specific distance.
|
||||
*
|
||||
* Note that `Data` has a `distance` field which allows specifying a distance
|
||||
* for each bond individually which takes precedence over this option.
|
||||
*/
|
||||
maxDistance: -1
|
||||
};
|
||||
export type Props = typeof DefaultProps
|
||||
|
||||
export function fromData(data: Data, props: Partial<Props> = {}): IndexPairBonds {
|
||||
@@ -61,11 +82,12 @@ export namespace IndexPairBonds {
|
||||
const { pairs, count } = data;
|
||||
const indexA = pairs.indexA.toArray() as ArrayLike<ElementIndex>;
|
||||
const indexB = pairs.indexB.toArray() as ArrayLike<ElementIndex>;
|
||||
const key = pairs.key && pairs.key.toArray();
|
||||
const order = pairs.order && pairs.order.toArray();
|
||||
const distance = pairs.distance && pairs.distance.toArray();
|
||||
const flag = pairs.flag && pairs.flag.toArray();
|
||||
return {
|
||||
bonds: getGraph(indexA, indexB, { order, distance, flag }, count),
|
||||
bonds: getGraph(indexA, indexB, { key, order, distance, flag }, count),
|
||||
maxDistance: p.maxDistance
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user