Compare commits

...

85 Commits

Author SHA1 Message Date
Alexander Rose
d98350cfb2 4.2.0 2024-05-04 18:52:14 -07:00
Alexander Rose
ae96b6cdac changelog 2024-05-04 18:49:40 -07:00
Alexander Rose
7ba6f5bf4c package updates 2024-05-04 18:49:03 -07:00
Alexander Rose
cede59c282 Merge pull request #1083 from molstar/emissive-bloom
Emissive & Bloom
2024-05-04 18:46:11 -07:00
Alexander Rose
87db52c339 Merge branch 'master' of https://github.com/molstar/molstar into emissive-bloom 2024-05-04 11:22:22 -07:00
Alexander Rose
b3d4500505 Merge pull request #1101 from midlik/load-mvs-keep-camera
Load mvs keep camera
2024-05-04 11:20:17 -07:00
Alexander Rose
2935f1340d Merge branch 'master' into load-mvs-keep-camera 2024-05-04 11:20:02 -07:00
Alexander Rose
fda3481d15 fix bumpiness artifact with xrayShaded 2024-05-04 10:50:36 -07:00
Alexander Rose
24ad3c875a handle identical sets cases in SetUTils 2024-05-04 10:48:27 -07:00
Alexander Rose
dd31c3041b fix bloom & transparent background
- need to blur alpha channel
2024-05-04 10:45:34 -07:00
Alexander Rose
8f3a3dd2be fix variable redeclaration 2024-05-04 10:43:56 -07:00
Alexander Rose
d9d378b249 Merge pull request #1109 from molstar/bond-assignment-fixes
bond assignment fixes
2024-04-29 22:13:51 -07:00
Alexander Rose
98119787e9 Merge branch 'master' into bond-assignment-fixes 2024-04-29 22:13:42 -07:00
Alexander Rose
b00f650066 Merge pull request #1110 from molstar/fix-bumpiness-1107
fix bumpiness artifact
2024-04-29 22:12:45 -07:00
dsehnal
24e7456d6d lint 2024-04-29 14:51:23 +02:00
Alexander Rose
0bb596e255 expand render-shape in tests/browser
- toggling spheres visibility
2024-04-28 18:29:15 -07:00
Alexander Rose
31a6eef1a4 expand render-shape in tests/browser 2024-04-28 18:22:13 -07:00
Alexander Rose
1deead40a5 schema updates 2024-04-28 14:38:06 -07:00
Alexander Rose
628f72903d package updates 2024-04-28 14:37:42 -07:00
Alexander Rose
5d8a569aef fix bumpiness artifact 2024-04-27 18:52:57 -07:00
Alexander Rose
9604b89ee0 workaround bloom artifacts
- getImageData: no undo of pre-multiplied alpha when bloom pass is disabled
2024-04-27 18:20:34 -07:00
Alexander Rose
4627d436a6 Merge branch 'master' of https://github.com/molstar/molstar into emissive-bloom 2024-04-27 15:37:46 -07:00
Alexander Rose
10cdd6a1f0 Merge pull request #1104 from molstar/ssao-artifacts
Fix SSAO artifacts (@corredD, #1082)
2024-04-27 15:36:00 -07:00
Alexander Rose
db9cb955b0 bond assignment fixes 2024-04-27 12:44:09 -07:00
Adam Midlik
00ad11dd6b MVS: camera positioning uses sceneRadiusFactor to avoid cropping 2024-04-26 16:18:46 +01:00
Ryan DiRisio
932b59d62c return selectors for each StateTransform in each measurement (#1087)
* return selectors for each StateTransform in each measurement

* update changelog, modify file header, add name to contributors

* consistently name return selections and labels

---------

Co-authored-by: Ryan DiRisio <rdirisio@treeline.bio>
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2024-04-25 16:10:21 +02:00
Alexander Rose
86b889d48b Merge branch 'master' of https://github.com/molstar/molstar into emissive-bloom 2024-04-24 21:33:19 -07:00
Alexander Rose
9521d47b4e Fix SSAO artifacts (@corredD, #1082) 2024-04-24 21:13:19 -07:00
Alexander Rose
032cf3ad8b typo 2024-04-24 20:48:54 -07:00
Alexander Rose
b39f9a772d changelog 2024-04-24 20:39:51 -07:00
Alexander Rose
ebe727ba24 fix render-spheres example
- wrong step size in sphere geometry boundingSphere & groupmapping
- handle empty instanceGridin renderer & renderable
2024-04-24 20:38:29 -07:00
Adam Midlik
b8551824bf Add missing await 2024-04-23 17:08:06 +01:00
Adam Midlik
8340fc0a98 MVS: fixed aggressive clipping when setting camera 2024-04-23 17:00:50 +01:00
dsehnal
10a3f92697 update gha 2024-04-22 16:10:44 +02:00
David Sehnal
da9f5927d8 Merge docs from molstar/docs (#1090)
* merge docs

* tweak actions
2024-04-22 16:00:26 +02:00
Alexander Rose
990dac6f8d handle xray-shaded in transparency check 2024-04-21 19:04:15 -07:00
Alexander Rose
1aa9c2fb22 disable emissive for transparency 2024-04-20 13:21:38 -07:00
Alexander Rose
77d5c71e61 fix bloom with transparent background 2024-04-20 13:20:13 -07:00
Adam Midlik
ae1d1a44f6 MVS: keepCamera parameter 2024-04-19 15:21:16 +01:00
Alexander Rose
904a409665 changelog 2024-04-14 16:32:53 -07:00
Alexander Rose
ab70536820 add emissive to mesoscale explorer 2024-04-14 16:16:05 -07:00
Alexander Rose
c9ef0171b4 add emissive to glb exporter 2024-04-14 16:15:57 -07:00
Alexander Rose
682b551b6e add bloom postprocessing effect 2024-04-14 16:15:38 -07:00
Alexander Rose
49cd8f7904 add emissive material support 2024-04-14 16:15:09 -07:00
Alexander Rose
a9e1373203 Merge pull request #1075 from molstar/transparency-render-refactor
refactor transparency rendering
2024-04-06 10:29:59 -07:00
Alexander Rose
368fd3a1d5 changelog 2024-04-06 10:20:48 -07:00
Alexander Rose
51ba661e54 cleanup 2024-04-06 10:20:04 -07:00
Alexander Rose
eb86b83aa5 Merge branch 'master' of https://github.com/molstar/molstar into transparency-render-refactor 2024-04-06 10:15:34 -07:00
Alexander Rose
16d5299fbd 4.1.0 2024-03-31 23:01:57 -07:00
Alexander Rose
c85aa6f979 changelog 2024-03-31 22:59:43 -07:00
Alexander Rose
dfab137696 schema updates 2024-03-31 22:59:05 -07:00
Alexander Rose
629fc65b81 package updates
- fix types
- fix webpack browserify issue
2024-03-31 22:56:54 -07:00
Alexander Rose
f06064a0c6 Merge pull request #1072 from hui2000ji/master
Reproduce pymol's surface_ramp_above_mode=1
2024-03-31 22:25:30 -07:00
Alexander Rose
fa70a0e5eb Merge branch 'master' into master 2024-03-31 22:20:44 -07:00
Alexander Rose
f1a2c5c4ee doc tweaks 2024-03-31 22:19:46 -07:00
hui2000ji
169d6323ca add Vec3.transformDirectionOffset; renam param in external-volume 2024-04-01 10:21:40 +08:00
Alexander Rose
c5b3db9960 refactor transparency rendering
- more uniform behavior for blended, wboit, dpoit
- fix issues with text & image geometry
2024-03-30 15:06:09 -07:00
Alexander Rose
fefed0a5f0 removed unused 2024-03-30 15:01:27 -07:00
Alexander Rose
d04311a989 fix type 2024-03-30 15:01:12 -07:00
Alexander Rose
0273df2c16 add rotation param to skybox 2024-03-30 08:19:16 -07:00
hui2000ji
05494953df add "normal" property to position location; add "Above Mode" to external-volume 2024-03-29 11:57:53 +08:00
midlik
d6b79de86d MVS docs reorganization (#1073) 2024-03-27 19:34:14 +01:00
hui2000ji
20327871a8 update mesh positionIt 2024-03-27 17:18:08 +08:00
Alexander Rose
dd0845038d Merge pull request #1069 from molstar/transparency-fix
Fix transparency rendering fallback
2024-03-26 22:07:18 -07:00
Alexander Rose
d5d3977506 Merge branch 'master' into transparency-fix 2024-03-26 22:07:08 -07:00
Alexander Rose
1675e18f57 Merge pull request #1070 from molstar/ssao-sampler-fix
Fix SSAO broken when `OES_texture_float_linear` is unavailable
2024-03-24 15:36:02 -07:00
Alexander Rose
d8bfe78b30 Fix SSAO broken when OES_texture_float_linear is unavailable 2024-03-24 14:56:24 -07:00
Alexander Rose
589d3465ef Merge pull request #1056 from pechersky/volume-transform
VolumeTransform to rotate a volume, like TransformStructureConformation
2024-03-24 10:14:11 -07:00
Alexander Rose
ebc63099b4 Merge branch 'master' into volume-transform 2024-03-24 10:13:57 -07:00
Alexander Rose
512a099a28 Fix transparency rendering fallback 2024-03-23 10:10:13 -07:00
midlik
d056bf3549 Fixed text rendering in headless mode (#1068)
* Fixed text rendering in headless mode

* lint
2024-03-21 06:46:46 +01:00
Alexander Rose
e2c929fa33 fix wrong width/height in InputObserver 2024-03-18 18:44:11 -07:00
David Sehnal
a619ae6687 BinaryCIF encoder fixes (#1057) 2024-03-04 17:10:02 +01:00
Yakov Pechersky
e40fc34a6f move changes to unreleased section 2024-03-04 09:07:49 -05:00
Yakov Pechersky
2734377d0a remove extra line 2024-03-01 15:51:03 -05:00
Yakov Pechersky
86575a7a15 Changelog 2024-03-01 15:50:09 -05:00
Yakov Pechersky
31227d2050 authors header 2024-03-01 15:50:03 -05:00
Yakov Pechersky
2e73a37de2 VolumeTransform to rotate a volume, like TransformStructureConformation 2024-03-01 15:27:37 -05:00
midlik
32c3ef0801 MVS: OpenApi schema in docs (#1051) 2024-02-19 18:04:06 +01:00
dsehnal
b70f2e073d 4.0.1 2024-02-19 12:56:41 +01:00
dsehnal
03b4aa1b33 changelog 2024-02-19 12:54:01 +01:00
Alexander Rose
0a4643ebe4 Revert "require WEBGL_depth_texture extension" 2024-02-17 11:26:55 -08:00
Alexander Rose
8dc3dae81e revert renderbuffer removal 2024-02-17 11:24:29 -08:00
David Sehnal
a1e2b0e5ae Bcif decoder fix (#1048)
* Fix bcif decoder

* update integerPackingSigned typing

* fix type fix
2024-02-14 18:23:22 +01:00
midlik
46af7d03bf MVSX (#1041)
* MVSX format provider

* MVS: Integration examples

* MVS: drag-and-drop support for MVSX

* MVS: support for URL param mvs-format=mvsx

* MVS: docs for MVSX

* MVS: mvs-render supports MVSX

* Update README
2024-02-14 18:22:27 +01:00
159 changed files with 4565 additions and 3279 deletions

View File

@@ -7,4 +7,5 @@
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
- [ ] Updated headers of modified files
- [ ] Added my name to `package.json`'s `contributors`
- [ ] Added my name to `package.json`'s `contributors`
- [ ] (Optional but encouraged) Improved documentation in `docs`

62
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Build & Deploy Docs
on:
push:
branches: master
paths:
- docs/**
- ".github/workflows/docs.yml"
pull_request:
branches: master
paths:
- docs/**
- ".github/workflows/docs.yml"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 👨🏼‍💻 checkout
uses: actions/checkout@v4
- name: 🐍 python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: ⛓️ dependencies
run: |
pip install mkdocs-material
- name: 🔧 build site
run: |
cd docs
mkdocs build
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/master'
steps:
- name: 👨🏼‍💻 checkout
uses: actions/checkout@v4
- name: 🐍 python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: ⛓️ dependencies
run: |
pip install mkdocs-material
- name: 🔧 build site
run: |
cd docs
mkdocs build
- name: 🚢 deploy docs
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
external_repository: molstar/docs
publish_branch: gh-pages
publish_dir: ./docs/site

View File

@@ -2,7 +2,9 @@ name: Build
on:
push:
branches: master
pull_request:
branches: master
jobs:
build:

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
build/
lib/
docs/site/
node_modules/
debug.log

View File

@@ -3,9 +3,43 @@ All notable changes to this project will be documented in this file, following t
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
## [v4.2.0] - 2023-04-05
- Add emissive material support
- Add bloom post-processing
- MolViewSpec extension: `loadMVS` supports `keepCamera` parameter
- Return StateTransform selectors from measurements API (addDistance, addAngle, etc.)
- Refactor transparency rendering
- More uniform behavior for blended, wboit, dpoit
- Fix issues with text & image geometry
- Fix render-spheres example (#1100)
- Wrong step size in sphere geometry boundingSphere & groupmapping
- Handle empty `instanceGrid` in renderer & renderable
- Fix bond assignment from `IndexPairBonds`
- Can not always be cached in `ElementSetIntraBondCache`
- Wrong operator checks in `findPairBonds`
- Fix SSAO artifacts (@corredD, #1082)
- Fix bumpiness artifacts (#1107, #1084)
## [v4.1.0] - 2023-03-31
- Add `VolumeTransform` to translate/rotate a volume like in a structure superposition
- Fix BinaryCIF encoder edge cases caused by re-encoding an existing BinaryCIF file
- Fix edge-case where width/height in InputObserver are not correct
- Fix transparency rendering fallback (#1058)
- Fix SSAO broken when `OES_texture_float_linear` is unavailable
- Add `normalOffset` to `external-volume` color theme
- This can give results similar to pymol's surface_ramp_above_mode=1
- Add `rotation` parameter to skybox background
## [v4.0.1] - 2023-02-19
- Fix BinaryCIF decoder edge cases. Fixes mmCIF model export from data provided by ModelServer.
- MolViewSpec extension: support for MVSX file format
- Revert "require WEBGL_depth_texture extension" & "remove renderbuffer use"
## [v4.0.0] - 2023-02-04
- Add Mesoscale Explorer app for investigating large systems

11
docs/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Mol* Developer Documentation
Contributions to the documentations are highly welcome! Please make a pull request with your changes.
Requires Python 3.x to build. From this directory:
```
pip install mkdocs-material
mkdocs serve
```

View File

@@ -0,0 +1,41 @@
# Convert CIF to BinaryCIF
BinaryCIF is an efficient, binary flavor of the CIF format. See [specification](https://github.com/molstar/BinaryCIF) and [publication](https://doi.org/10.1371/journal.pcbi.1008247) for further details.
This script reads data in CIF format and converts it lossless to a BinaryCIF file that can be read by Mol* or other
applications.
## Example
```sh
node lib/commonjs/cli/cif2bcif/index.js file.cif file.bcif
```
## Usage
| Argument | Description |
| --- | --- |
| `src` | Source CIF to convert (can be gzipped) |
| `out` | Generated BinaryCIF output path |
| `-c` | Path to optional config file |
| `-f` | Path to optional filter file |
```sh
index.js [-h] [-c CONFIG] [-f FILTER] src out
```
### Config file
Controls how certain columns will be encoded. This is a JSON array of instructions:
```ts
interface EncodingStrategyHint {
categoryName: string,
columnName: string,
encoding: 'pack' | 'rle' | 'delta' | 'delta-rle',
precision?: number
}
```
Identify a particular CIF columns by its name and override the encoding by Integer Packing, Run-Length Encoding, Delta
Encoding, or Delta & Run-Length Encoding. You can optionally control the precision if dealing with float values.
### Filter file
Specifies which categories and columns will be written. This is a plain text file, each line represents one entry.
You can specify explicitly which categories or columns to include by adding `category_name` or
`category_name.field_name`. You can also choose to ignore some categories or columns by adding `!category_name` or
`!category_name.field_name`.

View File

@@ -0,0 +1,25 @@
# Create Table from CCD
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) is as an external reference file describing
all residue and small molecule components found in PDB entries. The
[Protonation Variants Companion Dictionary (PVCD)](https://www.wwpdb.org/data/ccd) enumerates protonation variants of
canonical amino acids.
This script bundles all `chem_comp_bond` information from the CCD and the PVCD into a single file for later use.
Optionally, it can also generate a second output file that contains all `chem_comp_atom` information.
## Example
```sh
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-table.js build/data/ccb.bcif -b
```
## Usage
| Argument | Description |
| --- | --- |
| `out` | Generated file output path |
| `--forceDownload`, `-f` | Force download of CCD and PVCD |
| `--binary`, `-b` | Output as BinaryCIF |
| `--ccaOut`, `-a` | File output path of optionally generated chem_comp_atom |
```sh
create-table.js [-h] [--forceDownload] [--binary] [--ccaOut CCAOUT] out
```

View File

@@ -0,0 +1,20 @@
# Extract Ions from CCD
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) is as an external reference file describing
all residue and small molecule components found in PDB entries.
This script extracts all ions from the CCD and provides their names as TypeScript set.
## Example
```sh
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
```
## Usage
| Argument | Description |
| --- | --- |
| `out` | Generated file output path |
| `--forceDownload`, `-f` | Force download of CCD |
```sh
create-ions.js [-h] [--forceDownload] out
```

View File

@@ -0,0 +1,119 @@
# Model Server
Provides access to molecular 1D, 2D, and 3D (sub-)structure models of molecules. Substructures are described by the
mol-script (MolQL) language. It has the ability to include additional data to mmCIF “on the fly”, e.g. integrate
primary PDB archival data from [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd),
[Protonation Variants Companion Dictionary (PVCD)](https://www.wwpdb.org/data/ccd) and
[Biologically Interesting moleculeReference Dictionary (BIRD)](https://www.wwpdb.org/data/bird).
## Example
```sh
node lib/commonjs/servers/model/server --sourceMap pdb-bcif '/opt/data/bcif/${id}.bcif'
```
## Usage
| Argument | Description |
| --- | --- |
| `--version`, `-v` | Show program's version number and exit. |
| `--cfg` | JSON config file path. If a property is not specified, cmd line param/OS variable/default value are used. |
| `--printCfg` | Print current config for validation and exit. |
| `--cfgTemplate` | Prints default JSON config template to be modified and exit. |
| `--apiPrefix` | Specify the prefix of the API, i.e. &lt;host&gt;/&lt;apiPrefix&gt;/&lt;API queries&gt; |
| `--defaultPort` | Specify the port the server is running on |
| `--cacheMaxSizeInBytes` | Read structures are cached, this specifies the cache size, 0 for off. |
| `--cacheEntryTimeoutMs` | Specify in ms how long to keep entries in cache. |
| `--requestTimeoutMs` | The maximum number of ms the server spends on a request. |
| `--queryTimeoutMs` | The maximum time the server dedicates to executing a query in ms. Does not include the time it takes to read and export the data. |
| `--shutdownTimeoutMinutes` | Server will shut down after this amount of minutes, 0 for off. |
| `--shutdownTimeoutVarianceMinutes` | Modifies the shutdown timer by +/- `timeoutVarianceMinutes` (to avoid multiple instances shutting at the same time) |
| `--maxQueryManyQueries` | Maximum number of queries allowed by the query-many at a time |
| `--defaultSource` | modifies which 'sourceMap' source to use by default |
| `--sourceMap` | Map `id`s for a `source` to a file path. Example: `pdb-bcif '../../data/bcif/${id}.bcif'` - JS expressions can be used inside `${}`, e.g. `${id.substr(1, 2)}/${id}.mdb` Can be specified multiple times. The `SOURCE` variable (e.g. `pdb-bcif`) is arbitrary and depends on how you plan to use the server. Supported formats: cif, bcif, cif.gz, bcif.gz |
| `--sourceMapUrl` | Same as `--sourceMap` but for URL. `--sourceMapUrl src url format` Example: `pdb-cif "https://www.ebi.ac.uk/pdbe/entry-files/download/${id}_updated.cif" cif` Supported formats: cif, bcif, cif.gz, bcif.gz |
```sh
node lib/commonjs/servers/model/server [-h] [-v]
[--cfg CFG]
[--printCfg]
[--cfgTemplate]
[--apiPrefix PREFIX]
[--defaultPort PORT]
[--cacheMaxSizeInBytes CACHE_SIZE]
[--cacheEntryTimeoutMs CACHE_TIMEOUT]
[--requestTimeoutMs REQUEST_TIMEOUT]
[--queryTimeoutMs QUERY_TIMEOUT]
[--shutdownTimeoutMinutes TIME]
[--shutdownTimeoutVarianceMinutes VARIANCE]
[--maxQueryManyQueries QUERY_MANY_LIMIT]
[--defaultSource DEFAULT_SOURCE]
[--sourceMap SOURCE PATH]
[--sourceMapUrl SOURCE PATH SOURCE_MAP_FORMAT]
```
### Production Use
In production, it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
### Memory Issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
### Preprocessor Example
The preprocessor application allows addiing custom data to CIF files and/or
[convert CIF to BinaryCIF](./convert-to-bcif.md).
```sh
node lib/commonjs/servers/model/preprocess
```
### Preprocessor Usage
| Argument | Description |
| --- | --- |
| `--input`, `-i` | Input filename |
| `--outCIF`, `-oc` | Output CIF filename |
| `--outBCIF`, `-ob` | Output BinaryCIF filename |
| `--cfg`, `-c` | Config file path |
| `--folderIn`, `-fin` | Convert folder |
| `--folderOutCIF`, `-foc` | Convert folder text output |
| `--folderOutBCIF`, `-fob` | Convert folder binary output |
| `--folderNumProcesses`, `-fp` | Convert folder number processes |
Example cfg.json:
```ts
{
"numProcesses": 1,
"customProperties": {
"sources": [ "wwpdb" ],
"params": {
"wwPDB": {
"chemCompBondTablePath": "./build/data/ccb.bcif"
}
}
}
}
```
### Local Mode
The server can be run in local/file based mode using
```sh
node lib/commonjs/servers/model/query
```
### Custom Properties
This feature is still in development.
It is possible to provide property descriptors that transform data to internal representation and define how it should
be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are
linked to the server in the config and ``servers/model/properties``.
## From NPM
```
npm install --production molstar
cd ./model-server
```
(or ``node node_modules\.bin\model-server`` in Windows).
The NPM package contains all the tools mentioned in the previous sections as "binaries":
- ``model-server``
- ``model-server-query``
- ``model-server-preprocess``

View File

@@ -0,0 +1,22 @@
# Plugin State Server
Provides a simple backend for online storing and sharing of Mol* sessions used by
[``mol-plugin``](https://github.com/molstar/molstar/tree/master/src/mol-plugin) and
[``mol-state``](https://github.com/molstar/molstar/tree/master/src/mol-state) modules.
## Example
```sh
node lib/commonjs/servers/plugin-state --workding-folder ~
```
## Usage
| Argument | Description |
| --- | --- |
| `--working-folder` | Working folder path |
| `--port` | Server port. Alternatively, use ENV variable PORT. |
| `--api-prefix` | Server API prefix |
| `--max-states` | Maximum number of states to save |
```sh
node lib/commonjs/servers/plugin-state [-h] --working-folder WORKING_FOLDER [--port PORT] [--api-prefix API_PREFIX] [--max-states MAX_STATES]
```

View File

@@ -1,9 +1,9 @@
Zika Virus
==========
# VolumeServer Examples
## Zika Virus
![Zika Virus](img/zika_downsampled.png)
1TQN
====
## 1TQN
![1tqn](img/1tqn_downsampled.png)

View File

@@ -1,5 +1,4 @@
How it works
============
## VolumeServer: How it works
This document provides a high level overview of how the DensityServer works.

View File

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 292 KiB

View File

Before

Width:  |  Height:  |  Size: 310 KiB

After

Width:  |  Height:  |  Size: 310 KiB

View File

@@ -0,0 +1,126 @@
# VolumeServer
## What is VolumeServer
Provides near-instantaneous access to volumetric data including density maps (for instance, from X-ray crystallography
or cryo-electron microscopy experiments), spatial distribution data, output from electrostatic calculations. It works by
utilizing adaptive downsampling (similar to how Google Earth works).
It uses the text based CIF and BinaryCIF formats to deliver the data to the client.
For quick info about the benefits of using the server, check out the [examples](examples.md).
## Installing and Running
Requires nodejs 8+.
### From GitHub
```
git clone https://github.com/molstar/molstar
npm install
```
Afterwards, build the project source:
```
npm run build-tsc
```
and run the server by
```
node lib/commonjs/servers/volume/server
```
### From NPM
```
npm install --production molstar
./volume-server
```
(or ``node node_modules\.bin\volume-server`` in Windows).
The NPM package contains all the tools mentioned here as "binaries":
- ``volume-server``
- ``volume-server-pack``
- ``volume-server-query``
#### Production use
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
#### Memory issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
### Preparing the Data
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
### Local Mode
The program ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
### Navigating the Source Code
The source code is split into 2 mains parts: ``pack`` and ``server``:
- The ``pack`` part provides the means of converting CCP4 files into the internal block format.
- The ``server`` includes
- ``query``: the main part of the server that handles a query. ``execute.ts`` is the "entry point".
- ``algebra``: linear, "coordinate", and "box" algebra provides the means for calculations necessary to concent a user query into a menaningful response.
- API wrapper that handles the requests.
## Consuming the Data
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
The [Data Format](./response-data-format.md) document gives a detailed description of the server response format.
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
## Hosting the server
### Example
```sh
node lib/commonjs/servers/volume/server --idMap x-ray '/opt/data/xray/${id}.mdb'
```
### Usage
| Argument= | Description |
| --- | --- |
| `--cfg` | JSON config file path. If a property is not specified, cmd line param/OS variable/default value are used. |
| `--printCfg` | Print current config for validation and exit. |
| `--cfgTemplate` | Prints default JSON config template to be modified and exit. |
| `--apiPrefix` | Specify the prefix of the API, i.e. &lt;host&gt;/&lt;apiPrefix&gt;/&lt;API queries&gt; |
| `--defaultPort` | Specify the port the server is running on |
| `--shutdownTimeoutMinutes` | Server will shut down after this amount of minutes, 0 for off. |
| `--shutdownTimeoutVarianceMinutes` | Modifies the shutdown timer by +/- `timeoutVarianceMinutes` (to avoid multiple instances shutting at the same time) |
| `--idMap` | Map `id`s for a `type` to a file path. Example: `x-ray '../../data/mdb/xray/${id}-ccp4.mdb'` - JS expressions can be used inside `${}`, e.g. `${id.substr(1, 2)}/${id}.mdb` - Can be specified multiple times. - The `TYPE` variable (e.g. `x-ray`) is arbitrary and depends on how you plan to use the server. By default, Mol* Viewer uses `x-ray` and `em`, but any particular use case may vary. |
| `--maxRequestBlockCount` | Maximum number of blocks that could be read in 1 query. This is somewhat tied to the ``maxOutputSizeInVoxelCountByPrecisionLevel`` in that the `&lt;maximum number of voxel&gt; = maxRequestBlockCount * &lt;block size&gt;^3`. The default block size is 96 which corresponds to 28,311,552 voxels with 32 max blocks. |
| `--maxFractionalBoxVolume` | The maximum fractional volume of the query box (to prevent queries that are too big). |
| `--maxOutputSizeInVoxelCountByPrecisionLevel` | What is the (approximate) maximum desired size in voxel count by precision level - Rule of thumb: `&lt;response gzipped size&gt;` in `[&lt;voxel count&gt; / 8, &lt;voxel count&gt; / 4]`. The maximum number of voxels is tied to maxRequestBlockCount. |
```sh
node lib/commonjs/servers/volume/server [-h] [-v]
[--cfg CFG]
[--printCfg]
[--cfgTemplate]
[--apiPrefix PREFIX]
[--defaultPort PORT]
[--shutdownTimeoutMinutes TIME]
[--shutdownTimeoutVarianceMinutes VARIANCE]
[--idMap TYPE PATH]
[--maxRequestBlockCount COUNT]
[--maxFractionalBoxVolume VOLUME]
[--maxOutputSizeInVoxelCountByPrecisionLevel LEVEL [LEVEL ...]]
```

View File

@@ -1,10 +1,8 @@
Data Format
===========
# VolumeServer: Response Data Format
This document describes the CIF categories and fields generated by the server.
Query info
----------
## Query info
The reponse always contains a data block called ``SERVER`` with this format:
@@ -28,8 +26,7 @@ _density_server_result.query_box_b[1] 35.737
_density_server_result.query_box_b[2] 32.037001
```
Query data
----------
## Query data
If the query completed successfully with a non-empty result the response will contain one or more data blocks that correpond to the
"channels" present in the data (e.g. for x-ray data there will be ``2Fo-Fc`` and ``Fo-Fc``) channels.
@@ -41,6 +38,7 @@ data_2FO-FC
#
_volume_data_3d_info.name 2Fo-Fc
```
### Axis order
Axis order determines the order of axes of ``origin``, ``dimensions`` and ``sample_count`` fields. It also specifies

View File

@@ -0,0 +1,3 @@
# MolViewSpec
Please see the [standalone MolViewSpec documentation](https://molstar.org/mol-view-spec-docs/).

View File

@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
<script src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js"></script>
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />
</head>
<body>
<h1>Integration of Mol* with MolViewSpec Extension</h1>
<p>
This page demonstrates several methods to integrate Mol* Viewer in a web page and use MolViewSpec functionality.
See the source HTML to see the actual code.
</p>
<h2>Method 1: Get MVS view from a server and pass to the viewer</h2>
<p>
The recommended method is to serve the MVS view files by your server (either as static files or generated by the
server on-demand) and call the <code>loadMvsFromUrl</code> method to retrieve and load them.
This example uses a MVS view file from the address specified in the <code>sourceUrl</code> variable.
If the MVS view file contains relative references, they will be resolved as relative to <code>sourceUrl</code>.
</p>
<div id="viewer1" style="position: relative; width: 500px; height: 500px;"></div>
<script>
const sourceUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t_domain_labels.mvsj';
molstar.Viewer.create('viewer1', { layoutIsExpanded: false, layoutShowControls: false })
.then(viewer => viewer.loadMvsFromUrl(sourceUrl, 'mvsj'));
</script>
<p>
A variation of this method uses <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
<code>loadMvsFromUrl</code> and allows replacing the MVS view after it has been loaded.
</p>
<div id="viewer1b" style="position: relative; width: 500px; height: 500px;"></div>
<button onclick="loadView1();">View 1</button>
<button onclick="loadView2();">View 2</button>
<script>
let theViewer;
function load(viewer, url, replace) {
fetch(url)
.then(response => response.text())
.then(text => molstar.PluginExtensions.mvs.MVSData.fromMVSJ(text))
.then(mvsData => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: url, sanityChecks: true, replaceExisting: replace }));
}
function loadView1() {
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj', true);
}
function loadView2() {
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs-focus.mvsj', true);
}
molstar.Viewer.create('viewer1b', { layoutIsExpanded: false, layoutShowControls: false })
.then(viewer => {
theViewer = viewer;
loadView1();
});
</script>
<h2>Method 2: Construct MVS view on frontend and pass to the viewer</h2>
<p>
Another option is to utilize the MVS builder provided by the extension to build the view on frontend and then
pass it to the viewer. This example builds the view in plain JavaScript, directly in a &lt;script&gt; tag in
HTML. However, for a better developer experience consider writing the code in TypeScript.
If the built MVS view contains relative references, they will be resolved as relative to the URL of this HTML
page.
</p>
<div id="viewer2" style="position: relative; width: 500px; height: 500px;"></div>
<script>
// Build an ad-hoc MVS view
const builder = molstar.PluginExtensions.mvs.MVSData.createBuilder();
const structure = builder
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif' })
.parse({ format: 'bcif' })
.modelStructure({});
structure
.component({ selector: 'polymer' })
.representation({ type: 'cartoon' })
.color({ color: 'green' });
structure
.component({ selector: 'ligand' })
.label({ text: 'Retinoic acid' })
.focus({})
.representation({ type: 'ball_and_stick' })
.color({ color: '#cc3399' });
const mvsData = builder.getState();
// Initialize viewer and load MVSJ
const mvsj = molstar.PluginExtensions.mvs.MVSData.toMVSJ(mvsData);
molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
.then(viewer => viewer.loadMvsData(mvsj, 'mvsj'));
// // Alternative initialization and loading (avoids encoding and again decoding the data, allows changing the view by using `replaceExisting: true`):
// molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
// .then(viewer => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: undefined, sanityChecks: true, replaceExisting: false }));
</script>
<p>
Again, there is variation with using <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
<code>loadMvsData</code>.
</p>
</body>
</html>

43
docs/docs/index.md Normal file
View File

@@ -0,0 +1,43 @@
# Installation
## NPM Package
```
yarn add molstar
```
or
```
npm install molstar
```
Mol* code can then be imported from the ``molstar/lib/...`` namespace, e.g.
```ts
import { PluginContext } from 'molstar/lib/mol-plugin/context';
```
## Clone from GitHub
```
git clone https://github.com/molstar/molstar.git
cd molstar
npm install
npm build
```
--------------------
For a watch task to automatically rebuild the source code on changes, run
```
npm run watch
```
or if working just with the Viewer app for better performance
```
npm run watch-viewer
```

View File

@@ -1,3 +1,5 @@
# Interesting PDB Entries
* Cyclic polymers (1sfi, 6dny, 1HVZ)
* B-DNA (1bna)
* Missing carbonyl oxygen (1gfl)
@@ -44,7 +46,6 @@
* 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)
* 1RB8 (Assembly 1, I global)
* Assembly symmetries
* 5M30 (Assembly 1, C3 local and pseudo)
* 1RB8 (Assembly 1, I global)

View File

@@ -23,7 +23,7 @@ interface Snapshot {
When defining the state object, all components are optional, i.e., it is possible to define just the ``data`` component.
Example state is available [here](example-state.json). In the plugin, it is possible to create and load these objects using ``Download JSON``
Example state is available [here](./example-state.json). In the plugin, it is possible to create and load these objects using ``Download JSON``
and ``Open JSON`` buttons in the ``State Snapshots`` section.
# State Tree
@@ -69,7 +69,7 @@ interface Transform.Props {
}
```
"Built-in" data state transforms and description of their parameters are defined in ``mol-plugin/state/transforms``. Behavior transforms are defined in ``mol-plugin/behavior``. Auto-generated documentation for the transforms is also [available](transforms.md).
"Built-in" data state transforms and description of their parameters are defined in ``mol-plugin/state/transforms``. Behavior transforms are defined in ``mol-plugin/behavior``.
# Animation State

View File

@@ -0,0 +1,3 @@
# Plugin Examples
Refer to Mol* [Apps](https://github.com/molstar/molstar/tree/master/src/apps) and [Examples](https://github.com/molstar/molstar/tree/master/src/examples).

View File

@@ -0,0 +1,274 @@
# Creating Plugin Instance
## Intro
What is a plugin? A plugin is a collection of modules that provide functionality to the `Mol*` UI. The plugin is responsible for managing the state of the viewer, internal and user interactions. It has been a previous point of confusion for new users of `Mol*` to associate the __viewer__ part of the library with what is further referred to as the __plugin__. These two are closely connected in the `molstar-plugin-ui` module, which is the user-facing part of the library and ultimately provides the viewer, but they are ultimately distinct.
It is recommended that you inspect the general class structure of [`PluginInitWrapper`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/plugin.tsx#L41), [`PluginUIContext`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin/context.ts#L71) and [`PluginUIComponent`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/base.tsx#L16) to better understand the flow of data and events in the plugin.
A passing analogy is that a [ `PluginContext` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin/context.ts#L71) is the engine that powers computation, rendering, events and subscriptions inside the molstar UI. All UI components depend on `PluginContext`.
There are 4 basic ways of instantiating the Mol* plugin.
## ``Viewer`` wrapper
- The most basic usage is to use the ``Viewer`` wrapper. This is best suited for use cases that do not require much custom behavior and are mostly about just displaying a structure.
- See ``Viewer`` class is defined in [src/apps/viewer/app.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/app.ts) for available methods and options.
Example usage without using WebPack:
```HTML
<style>
#app {
position: absolute;
left: 100px;
top: 100px;
width: 800px;
height: 600px;
}
</style>
<!--
molstar.js and .css are obtained from
- the folder build/viewer after cloning and building the molstar package
- from the build/viewer folder in the Mol* NPM package
-->
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./molstar.js"></script>
<div id="app"></div>
<script type="text/javascript">
molstar.Viewer.create('app', {
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
viewportShowExpand: true,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
pdbProvider: 'rcsb',
emdbProvider: 'rcsb',
}).then(viewer => {
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
});
</script>
```
When using WebPack (or possibly other build tool) with the Mol* NPM package installed, the viewer class can be imported using
```ts
import { Viewer } from 'molstar/build/viewer/molstar'
function initViewer(target: string | HTMLElement) {
return new Viewer(target, { /* options */})
}
```
## ``PluginContext`` with built-in React UI
- For more customization options it is possible to use the [``PluginContext``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/context.ts) directly.
- When creating the plugin instance it is possible to customize the [``PluginSpec``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts).
- The default [``PluginSpec``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts) is available [here](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts).
- [``PluginConfig``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/config.ts) object provides additional customization options.
- See the [Viewer State Management](viewer-state.md) section for more information on customizing things like background.
- See the [Data State Management](data-state.md) section for more information on build the state.
```ts
import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
import { createPluginUI } from 'molstar/lib/mol-plugin-ui';
import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
const MySpec: PluginUISpec = {
...DefaultPluginUISpec(),
config: [
[PluginConfig.VolumeStreaming.Enabled, false]
]
}
async function createPlugin(parent: HTMLElement) {
const plugin = await createPluginUI({
target: parent,
spec: MySpec,
render: renderReact18
});
const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
return plugin;
}
createPlugin(document.getElementById('app')!); // app is a <div> element with position: relative
```
To use the plugin (with the React UI) inside another React app:
A single-plugin setup is shown the example below. In order to initialize multiple
plugins, each with its own context and viewport, some extra steps are required (docs section to be added).
```ts
import { useEffect, createRef } from "react";
import { createPluginUI } from "molstar/lib/mol-plugin-ui";
import { renderReact18 } from "molstar/lib/mol-plugin-ui/react18";
import { PluginUIContext } from "molstar/lib/mol-plugin-ui/context";
/* Might require extra configuration,
see https://webpack.js.org/loaders/sass-loader/ for example.
create-react-app should support this natively. */
import "molstar/lib/mol-plugin-ui/skin/light.scss";
declare global {
interface Window {
molstar?: PluginUIContext;
}
}
export function MolStarWrapper() {
const parent = createRef<HTMLDivElement>();
// In debug mode of react's strict mode, this code will
// be called twice in a row, which might result in unexpected behavior.
useEffect(() => {
async function init() {
window.molstar = await createPluginUI({
target: parent.current as HTMLDivElement,
render: renderReact18
});
const data = await window.molstar.builders.data.download(
{ url: "https://files.rcsb.org/download/3PTB.pdb" }, /* replace with your URL */
{ state: { isGhost: true } }
);
const trajectory =
await window.molstar.builders.structure.parseTrajectory(data, "pdb");
await window.molstar.builders.structure.hierarchy.applyPreset(
trajectory,
"default"
);
}
init();
return () => {
window.molstar?.dispose();
window.molstar = undefined;
};
}, []);
return <div ref={parent} style={{ width: 640, height: 480 }}/>;
}
```
Furthermore, if it is desirable in your project to use the `molstar`'s React UI components, but you wish to alter or rearrange the layout, you should take a look at the signatures of [ `PluginUIComponent` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/base.tsx#L16) which every "control" subclasses.
[ `SequenceView` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/sequence.tsx#L221C4-L221C4), for example, can be used separately from the `PluginUI`. Yet you would need to pass the `PluginUIContext` to it in order for it to observe the changes in the state of the plugin. This can be done via a `PluginContextContainer`:
```typescript
// your_app.plugin: PluginUIContext
...
<div className="your_custom_ui">
<PluginContextContainer plugin={your_app.plugin}>
<SequenceView />
</PluginContextContainer>
</div>
```
## Directly using Mol* React UI
```ts
class MolStarWrapper {
private resolveInit: () => void;
initialized = new Promise<boolean>(res => { this.resolveInit = () => res(true); });
private initCalled = false;
plugin: PluginUIContext;
async init() {
if (this.initCalled) return;
this.initCalled = true;
this.plugin = ...;
await this.plugin.init();
this.resolveInit();
}
}
function MolStar({ model }: { model: MolStarWrapper }) {
const [initialized, setInitialized] = useState(false);
useEffect(() => {
async function init() {
await model.init();
setInitialized(true);
}
init();
}, [model]);
if (!initialized) return <>Loading</>;
return <div style={{ ..., position: 'relative' }}>
<Plugin plugin={model.plugin} />
</div>;
}
```
## ``PluginContext`` without built-in React UI
- The [``PluginContext``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/context.ts) can be instantiated without using the default React UI.
```HTML
<div id='molstar-parent' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'>
<canvas id='molstar-canvas' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'></canvas>
</div>
```
```ts
import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
import { PluginContext } from 'molstar/lib/mol-plugin/context';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
const MySpec: PluginSpec = {
...DefaultPluginSpec(),
config: [
[PluginConfig.VolumeStreaming.Enabled, false]
]
}
async function init() {
const plugin = new PluginContext(MySpec);
await plugin.init();
const canvas = <HTMLCanvasElement> document.getElementById('molstar-canvas');
const parent = <HTMLDivElement> document.getElementById('molstar-parent');
if (!plugin.initViewer(canvas, parent)) {
console.error('Failed to init Mol*');
return;
}
// Example url:"https://files.rcsb.org/download/3j7z.pdb"
// Example url:"https://files.rcsb.org/download/5AFI.cif"
const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format); //format is 'mmcif' or 'pdb' etc.
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
```
## ``Canvas3D`` without built-in state management
- The ``PluginContext`` object from the above examples can be completely omitted.
- See [Browser Tests](https://github.com/molstar/molstar/tree/master/src/tests/browser) for example usage.
```ts
const canvas = document.getElementById('canvas'); // parent <canvas> element
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
// use the canvas3d object here
```

View File

@@ -0,0 +1,110 @@
# Selections
Assuming you have a model already loaded into the plugin (see [Creating Plugin Instance](./instance.md)), these are some of the following method you can select structural data.
### Selecting directly from the `hierarchy` manager
One can select a subcomponent's data directly from the plugin manager.
```typescript
import { Structure } from '../mol-model/structure';
const ligandData = plugin.managers.structure.hierarchy.selection.structures[0]?.components[0]?.cell.obj?.data;
const ligandLoci = Structure.toStructureElementLoci(ligandData as any);
plugin.managers.camera.focusLoci(ligandLoci);
plugin.managers.interactivity.lociSelects.select({ loci: ligandLoci });
```
## Selection callbacks
If you want to subscribe to selection events (e.g. to change external state in your application based on a user selection), you can use: `plugin.behaviors.interaction.click.subscribe`
Here's an example of passing in a React "set" function to update selected residue positions.
```typescript
import {
Structure,
StructureProperties,
} from "molstar/lib/mol-model/structure"
// setSelected is assumed to be a "set" function returned by useState
// (selected: any[]) => void
plugin.behaviors.interaction.click.subscribe(
(event: InteractivityManager.ClickEvent) => {
const selections = Array.from(
plugin.managers.structure.selection.entries.values()
);
// This bit can be customized to record any piece information you want
const localSelected: any[] = [];
for (const { structure } of selections) {
if (!structure) continue;
Structure.eachAtomicHierarchyElement(structure, {
residue: (loc) => {
const position = StructureProperties.residue.label_seq_id(loc);
localSelected.push({ position });
},
});
}
setSelected(localSelected);
}
)
```
### `Molscript` language
Molscript is a language for addressing crystallographic structures and is a part of the Mol* library found at `https://github.com/molstar/molstar/tree/master/src/mol-script`. It can be used against the Molstar plugin as a query language and transpiled against multiple external molecular visualization libraries(see [here](https://github.com/molstar/molstar/tree/master/src/mol-script/transpilers)).
### Querying a structure for a specific chain and residue range (select residues with 12<res_id<200 of chain with auth_asym_id==A) :
```typescript
import { compileIdListSelection } from 'molstar/lib/mol-script/util/id-list'
const query = compileIdListSelection('A 12-200', 'auth');
window.molstar?.managers.structure.selection.fromCompiledQuery('add',query);
```
## Selection Queries
Another way to create a selection is via a `SelectionQuery` object. This is a more programmatic way to create a selection. The following example shows how to select a chain and a residue range using a `SelectionQuery` object.
This relies on the concept of `Expression` which is basically a intermediate representation between a Molscript statement and a selection query.
### Select residues 10-15 of chains A and F in a structure using a `SelectionQuery` object:
```typescript
import { MolScriptBuilder as MS, MolScriptBuilder } from 'molstar/lib/mol-script/language/builder';
import { Expression } from 'molstar/lib/mol-script/language/expression';
import { StructureSelectionQuery } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query'
export function select_multiple() {
const args = [['A', 10, 15], ['F', 10, 15]]
const groups: Expression[] = [];
for (var chain of args) {
groups.push(MS.struct.generator.atomGroups({
"chain-test": MS.core.rel.eq([MolScriptBuilder.struct.atomProperty.macromolecular.auth_asym_id(), chain[0]]),
"residue-test": MS.core.rel.inRange([MolScriptBuilder.struct.atomProperty.macromolecular.label_seq_id(), chain[1], chain[2]])
}));
}
var sq = StructureSelectionQuery('residue_range_10_15_in_A_and_F', MS.struct.combinator.merge(groups))
mstar.managers.structure.selection.fromSelectionQuery('set', sq)
}
```
Complex queries can be constructed by combining primitive queries at the level of [`chain-test`, `residue-test`, `entity-test`, etc] (https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-script/language/symbol-table/structure-query.ts#L88C4-L94C112) by combining them via logical connectives provided in the `MolscriptBuilder.core.rel` as above.
Inspect these examples to get a better feeling for this syntax: `https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-state/helpers/structure-selection-query.ts#L88-L580`
Furthermore, a query made this way can be converted to a `Loci` object which is important in many parts of the libary:
```typescript
// Select residue 124 of chain A and convert to Loci
const Q = MolScriptBuilder;
var sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'chain-test' : Q.core.rel.eq([Q.struct.atomProperty.macromolecular.auth_asym_id(), A]),
"residue-test": Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), 124]),
}), objdata)
let loci = StructureSelection.toLociWithSourceUnits(sel);
```

View File

@@ -0,0 +1,72 @@
# Load Trajectory from a Custom Format
This section shows a high level example for loading trajectory from custom data in specialized plugin instances. A more complete solution is available for example in form of the [G3D format extension](https://github.com/molstar/molstar/tree/master/src/extensions/g3d).
## Defining and Using a Custom Transformer
```ts
import { StateTransformer } from 'molstar/lib/mol-state';
const CreateTransformer = StateTransformer.builderFactory('custom-namespace');
export interface CustomTrajectoryData {
// ...
}
export const TrajectoryFromCustomData = CreateTransformer({
name: 'trajectory-from-custom-data',
display: 'Trajectory',
from: PluginStateObject.Root,
to: PluginStateObject.Molecule.Trajectory,
params: {
data: PD.Value<CustomTrajectoryData>(void 0 as any, { isHidden: true }),
},
})({
apply({ params }) {
return Task.create('Trajectory', async (ctx) => {
const models = await customParse(params.data, ctx);
return new PluginStateObject.Molecule.Trajectory(models, {
label: 'Trajectory',
});
});
},
});
```
The ``customParse`` function can usually be implemented
by modifying/extending an [existing parser already available in Mol*](https://github.com/molstar/molstar/tree/master/src/mol-model-formats/structure).
To use the transformer:
```ts
const data: CustomTrajectoryData = await (await fetch(url)).json();
const trajectory = await plugin.build().toRoot().apply(TrajectoryFromCustomData, { data }).commit();
// Create the representation
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
```
## Using Mol* to Download the Data
```ts
export const TrajectoryFromCustomData = CreateTransformer({
name: 'trajectory-from-custom-data',
display: 'Trajectory',
from: PluginStateObject.Data.String, // or PluginStateObject.Data.Binary
to: PluginStateObject.Molecule.Trajectory,
})({
apply({ a }) {
return Task.create('Trajectory', async (ctx) => {
const models = await customParse(a.data, ctx);
return new PluginStateObject.Molecule.Trajectory(models, {
label: 'Trajectory',
});
});
},
});
//////////////
const data = await plugin.builders.data.download({ url, isBinary });
const trajectory = await plugin.build().to(data).apply(TrajectoryFromCustomData, { data }).commit();
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
```

View File

@@ -0,0 +1,132 @@
# Viewer State Management
## ``Canvas3D`` Properties
Properties of the [``Canvas3D``](https://github.com/molstar/molstar/blob/master/src/mol-canvas3d/canvas3d.ts) can be
changed using [``PluginCommands``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/commands.ts).
### Change background, highlight, or select color
```ts
import { ColorNames } from 'molstar/lib/mol-util/color/names';
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
const renderer = plugin.canvas3d!.props.renderer;
PluginCommands.Canvas3D.SetSettings(plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.red /* or: 0xff0000 as Color */ } } });
```
Similarly, `highlightColor` and `selectColor` can be updated.
## Interactivity
Interactivity in Mol* is based on the concept of ``Loci``. A ``Loci`` usually references a collection of objects and can be created by a [``Selection``](selections.md). For example, the
``Loci`` captures all atoms in the chain with label_asym_id B of a protein:
```ts
import { Script } from 'molstar/lib/mol-script/script';
import { StructureSelection } from 'molstar/lib/mol-model/structure/query';
const data = plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data;
if (!data) return;
const selection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'chain-test': Q.core.rel.eq(['B', Q.ammp('label_asym_id')])
}), data);
const loci = StructureSelection.toLociWithSourceUnits(selection);
```
A ``Loci`` can be used to trigger custom [``Behaviors``](#behaviors).
### Log message to Mol* console
The built-in console in the bottom center of the plugin shows log entries.
```ts
plugin.log.message('This message will appear in the Mol* console');
```
Other log levels are: `info`, `warn`, and `error`.
### Show toast message
Toast messages will appear in the bottom right of the plugin and will linger for a limited time before disappearing.
```ts
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
PluginCommands.Toast.Show(plugin, {
title: 'Custom Message',
message: 'A custom toast message that will disappear after 2 seconds.',
key: 'toast-custom',
timeoutMs: 2000
});
```
## Behaviors
The state of the Mol* plugin is usually governed by dynamic behaviors which can be set up in initial plugin specification or updated during the plugin runtime. This allows for high modularity and customizability of individual plugin instances.
### Highlight ``Loci``
Highlighting adds a transient overpaint to a representation that will linger until the mouse enters hovers over another
object. Highlights can be applied to a previously defined ``Loci`` by:
```ts
plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }); // loci: Loci
```
Reset all highlights by:
```ts
plugin.managers.interactivity.clearHighlights();
```
### Select ``Loci``
Selected elements will appear with distinct visuals and, if applicable, the corresponding sequence positions will be
shown in the Sequence Viewer panel. Selections persist until removed, for example by clicking the background. A ``Loci``
is selected by:
```ts
plugin.managers.interactivity.lociSelects.select({ loci }); // loci: Loci
```
Deselect a specific ``Loci`` by:
```ts
plugin.managers.interactivity.lociSelects.deselect({ loci }); // loci: Loci
```
To deselect everything:
```ts
plugin.managers.interactivity.lociSelects.deselectAll();
```
### Focus ``Loci``
The focus representation shows a ``Loci`` in ball-and-stick representation and, additionally, visualizes non-covalent
interactions between atoms of the ``Loci`` as well as interactions with surrounding residues (default: 5 Å).
```ts
plugin.managers.structure.focus.setFromLoci(loci);
```
Extend an existing focus representation by:
```ts
plugin.managers.structure.focus.addFromLoci(loci); // loci: Loci
```
Reset by:
```ts
plugin.managers.structure.focus.clear();
```
### Zoom ``Loci``
A ``Loci`` can also be used to manipulate the camera. Zoom in by:
```ts
plugin.managers.camera.focusLoci(loci); // loci: Loci
```
Restore the default camera position by:
```ts
plugin.managers.camera.reset();
```
### Turn off view resetting on new representations
A new representation via something like
```ts
.apply(StateTransforms.Representation.VolumeRepresentation3D, ...)
```
can reset the view to make the whole representation visible.
When one wants to keep the view the same instead of having the rep reset the view,
keep the view constant by:
```ts
plugin.canvas3d?.setProps({ camera: { manualReset: true } });
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,161 +0,0 @@
# Mol* MolViewSpec extension
**MolViewSpec (MVS)** is a tool for standardized description of reproducible molecular visualizations shareable across software applications.
MolViewSpec provides a generic description of typical visual scenes that may occur as part of molecular visualizations. A tree format allows the composition of complex scene descriptors by combining reoccurring nodes that serve as building blocks.
## More sources:
- MolViewSpec home page: https://molstar.org/mol-view-spec/
- Python library `molviewspec` for building MolViewSpec views: https://pypi.org/project/molviewspec/
- Python library `molviewspec` in action: https://colab.research.google.com/drive/1O2TldXlS01s-YgkD9gy87vWsfCBTYuz9
## MolViewSpec data structure
MVS is based on a tree format, i.e. a molecular view is described as a tree where individual node types represent common data operations needed to create the view (e.g. download, parse, color). Each node can have parameters that provide additional details for the operation.
A simple example of a MVS tree showing PDB structure 1cbs:
![Example MolViewSpec - 1cbs with labelled protein and ligand](./1cbs.png "Example MolViewSpec")
```txt
- root {}
- download {url: "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"}
- parse {format: "bcif"}
- structure {type: "model"}
- component {selector: "polymer"}
- representation {type: "cartoon"}
- color {color: "green"}
- color {selector: {label_asym_id: "A", beg_label_seq_id: 1, end_label_seq_id: 50}, color: "#6688ff"}
- label {text: "Protein"}
- component {selector: "ligand"}
- representation {type: "ball_and_stick"}
- color {color: "#cc3399"}
- label {text: "Retinoic Acid"}
- canvas {background_color: "#ffffee"}
- camera {target: [17,21,27], position: [41,34,69], up: [-0.129,0.966,-0.224]}
```
(This is just a human-friendly representation of the tree, not the actual data format!)
A complete list of supported node types and their parameters is described by the [MVS tree schema](./mvs-tree-schema.md).
### Encoding
A MolViewSpec tree can be encoded and stored in `.mvsj` format, which is basically a JSON representation of the tree with additional metadata:
```json
{
"metadata": {
"title": "Example MolViewSpec - 1cbs with labelled protein and ligand",
"version": "1",
"timestamp": "2023-11-24T10:38:17.483Z"
},
"root": {
"kind": "root",
"children": [
{
"kind": "download",
"params": {"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"},
"children": [
{
"kind": "parse",
"params": {"format": "bcif"},
"children": [
...
```
Complete file: [1cbs.mvsj](../../../examples/mvs/1cbs.mvsj)
## MolViewSpec extension functionality
Mol* MolViewSpec extension provides functionality for building, validating, and visualizing MVS views.
### Graphical user interface
- **Drag&drop support:** The easiest way to load a MVS view into Mol* Viewer is to drag a `.mvsj` file and drop it in a browser window with Mol* Viewer.
- **Load via menu:** Another way to load a MVS view is to use "Download File" or "Open Files" action, available in the "Home" tab in the left panel. For these actions, the "Format" parameter must be set to "MVSJ" (in the "Miscellaneous" category) or "Auto".
- **URL parameters:** Mol* Viewer supports `mvs-url`, `mvs-data`, and `mvs-format` URL parameters to specify a MVS view to be loaded when the viewer is initialized.
- `mvs-url` specifies the address from which the MVS view should be retrieved.
- `mvs-data` specifies the MVS view data directly. Keep in mind that some characters must be escaped to be used in the URL. Also beware that URLs longer than 2000 character may not work in all browsers.
- `mvs-format` specifies the format of the MVS view data (from `mvs-url` or `mvs-data`). The only allowed (and default) value is `mvsj`, as this is currently the only supported format.
Examples of URL parameter usage:
- https://molstar.org/viewer?mvs-format=mvsj&mvs-url=https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj
- https://molstar.org/viewer?mvs-format=mvsj&mvs-data=%7B%22metadata%22%3A%7B%22title%22%3A%22Example%20MolViewSpec%20-%201cbs%20with%20labelled%20protein%20and%20ligand%22%2C%22version%22%3A%221%22%2C%22timestamp%22%3A%222023-11-24T10%3A38%3A17.483%22%7D%2C%22root%22%3A%7B%22kind%22%3A%22root%22%2C%22children%22%3A%5B%7B%22kind%22%3A%22download%22%2C%22params%22%3A%7B%22url%22%3A%22https%3A//www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22parse%22%2C%22params%22%3A%7B%22format%22%3A%22bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22structure%22%2C%22params%22%3A%7B%22type%22%3A%22model%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22polymer%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22cartoon%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22green%22%7D%7D%2C%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22selector%22%3A%7B%22label_asym_id%22%3A%22A%22%2C%22beg_label_seq_id%22%3A1%2C%22end_label_seq_id%22%3A50%7D%2C%22color%22%3A%22%236688ff%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Protein%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22ligand%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22ball_and_stick%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22%23cc3399%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Retinoic%20Acid%22%7D%7D%5D%7D%5D%7D%5D%7D%5D%7D%2C%7B%22kind%22%3A%22canvas%22%2C%22params%22%3A%7B%22background_color%22%3A%22%23ffffee%22%7D%7D%2C%7B%22kind%22%3A%22camera%22%2C%22params%22%3A%7B%22target%22%3A%5B17%2C21%2C27%5D%2C%22position%22%3A%5B41%2C34%2C69%5D%2C%22up%22%3A%5B-0.129%2C0.966%2C-0.224%5D%7D%7D%5D%7D%7D
### Programming interface
Most functions for manipulation of MVS data (including parsing, encoding, validating, and building) are provided by the `MVSData` object (defined in [src/extensions/mvs/mvs-data.ts](/src/extensions/mvs/mvs-data.ts)). In TypeScript, `MVSData` is also the type for a MVS view.
The `loadMVS` function (defined in [src/extensions/mvs/load.ts](/src/extensions/mvs/load.ts)) can be used to load MVS view data into Mol* Viewer.
Example usage:
```ts
// Fetch a MVS, validate, and load
const response = await fetch('https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj');
const rawData = await response.text();
const mvsData: MVSData = MVSData.fromMVSJ(rawData);
if (!MVSData.isValid(mvsData)) throw new Error(`Oh no: ${MVSData.validationIssues(mvsData)}`);
await loadMVS(this.plugin, mvsData, { replaceExisting: true });
console.log('Loaded this:', MVSData.toPrettyString(mvsData));
console.log('Loaded this:', MVSData.toMVSJ(mvsData));
// Build a MVS and load
const builder = MVSData.createBuilder();
const structure = builder
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/download/1og2_updated.cif' })
.parse({ format: 'mmcif' })
.modelStructure();
structure
.component({ selector: 'polymer' })
.representation({ type: 'cartoon' });
structure
.component({ selector: 'ligand' })
.representation({ type: 'ball_and_stick' })
.color({ color: '#aa55ff' });
const mvsData2: MVSData = builder.getState();
await loadMVS(this.plugin, mvsData2, { replaceExisting: false });
```
When using the pre-built Mol* plugin bundle, `MVSData` and `loadMVS` are exposed as `molstar.PluginExtensions.mvs.MVSData` and `molstar.PluginExtensions.mvs.loadMVS`. Furthermore, the `molstar.Viewer` class has `loadMvsFromUrl` and `loadMvsData` methods, providing the same functionality as `mvs-url` and `mvs-data` URL parameters.
### Command-line utilities
The MVS extension in Mol* provides a few command-line utilities, which can be executed via NodeJS:
- `mvs-validate` provides validation of MolViewSpec files
- `mvs-render` creates images based on MolViewSpec files
- `mvs-print-schema` prints MolViewSpec tree schema (i.e. currently supported node types and their parameters)
Example usage:
```sh
# Validate a MolViewSpec file `examples/mvs/1cbs.mvsj`
node lib/commonjs/cli/mvs/mvs-validate examples/mvs/1cbs.mvsj
# Render a MolViewSpec file `examples/mvs/1cbs.mvsj` to `../outputs/1cbs.png`
npm install --no-save canvas gl jpeg-js pngjs # Might be needed before the first execution
node lib/commonjs/cli/mvs/mvs-render -i examples/mvs/1cbs.mvsj -o ../outputs/1cbs.png --size 800x600 --molj
# Print MolViewSpec tree schema formatted as markdown
node lib/commonjs/cli/mvs/mvs-print-schema --markdown
```
(If you installed Mol* package from the npm repository, use can just type `npx mvs-validate`...).
## Topics
- [Selectors](./selectors.md)
- [Annotations](./annotations.md)
- [Camera Settings](./camera-settings.md)

View File

@@ -1,185 +0,0 @@
# MVS annotations
Annotations are used to define substructures (components) and apply colors, labels, or tooltips to them. In contrast to [selectors](./selectors.md), annotations are defined in a separate file, which can then be referenced in the main MVS file.
## MVS annotation files
MVS annotations can be encoded in multiple different formats, but their logic is always the same and in fact very similar to that of selectors.
### JSON format
The simplest example of an annotation in JSON format is just a JSON-encoded [union component expression](./selectors.md) selector. Here is a simple annotation containing 4 **annotation rows**:
```json
[
{ "label_asym_id": "A" },
{ "label_asym_id": "B" },
{ "label_asym_id": "B", "beg_label_seq_id": 100, "end_label_seq_id": 200 },
{ "label_asym_id": "B", "beg_label_seq_id": 150, "end_label_seq_id": 160 },
]
```
However, in a typical annotation, there is at least one extra field that provides the value of the dependent variable (such as color or label) mapped to each annotation row:
```json
[
{ "label_asym_id": "A", "color": "#00ff00" },
{ "label_asym_id": "B", "color": "blue" },
{ "label_asym_id": "B", "beg_label_seq_id": 100, "end_label_seq_id": 200, "color": "skyblue" }
{ "label_asym_id": "B", "beg_label_seq_id": 150, "end_label_seq_id": 160, "color": "lightblue" }
]
```
This particular annotation (when applied via `color_from_uri` node) will apply green color (#00ff00) to the whole chain A and three shades of blue to the chain B. Later annotation rows override earlier rows, therefore residues 199 will be blue, 100149 skyblue, 150160 lightblue, 161200 skyblue, and 201end blue. (Tip: to color all the rest of the structure in one color, add an annotation row with no selector fields (e.g. `{ "color": "yellow" }`) to the beginning of the annotation.)
Real-life annotation files can include huge numbers of annotation rows. To avoid repeating the same field keys in every row, we can convert the array-of-objects into object-of-arrays. This will result in an equivalent annotation but smaller file size:
```json
{
"label_asym_id": ["A", "B", "B", "B"],
"beg_label_seq_id": [null, null, 100, 150],
"end_label_seq_id": [null, null, 200, 160],
"color": ["#00ff00", "blue", "skyblue", "lightblue"]
}
```
A more complex example of JSON annotation is provided in [/examples/mvs/1h9t_domains.json](/examples/mvs/1h9t_domains.json).
### CIF format
Annotations can also be encoded using CIF format, a table-based format which is commonly used in structure biology to store structures or any kind of tabular data.
The example from above, encoded as CIF, would look like this:
```cif
data_annotation
loop_
_coloring.label_asym_id
_coloring.beg_label_seq_id
_coloring.end_label_seq_id
_coloring.color
A . . '#00ff00'
B . . 'blue'
B 100 200 'skyblue'
B 150 160 'lightblue'
```
An advantage of the CIF format is that it can include multiple annotation tables in the same file, organized into blocks and categories. Then the MVS file can reference individual tables using `block_header` (or `block_index`) and `category_name` parameters. The column containing the dependent variable can be specified using `field_name` parameter. In this case, we could use `"block_header": "annotation", "category_name": "coloring", "field_name": "color"`.
### BCIF format
This has exactly the same structure as the CIF format, but encoded using [BinaryCIF](https://github.com/molstar/BinaryCIF).
## Referencing MVS annotations in MVS tree
### From URI
MVS annotations can be referenced in `color_from_uri`, `label_from_uri`, `tooltip_from_uri`, and `component_from_uri` nodes in MVS tree.
For example this part of a MVS tree:
```txt
- representation {type: "cartoon"}
- color {selector: {label_asym_id: "A"}, color: "#00ff00"}
- color {selector: {label_asym_id: "B"}, color: "blue"}
- color {selector: {label_asym_id: "B", beg_label_seq_id: 100, end_label_seq_id: 200}, color: "skyblue"}
- color {selector: {label_asym_id: "B", beg_label_seq_id: 150, end_label_seq_id: 160}, color: "lightblue"}
```
can be replaced by:
```txt
- representation {type: "cartoon"}
- color_from_uri {uri: "https://example.org/annotations.json", format: "json", schema: "residue_range"}
```
assuming that the JSON annotation file shown in the previous section is available at `https://example.org/annotations.json`.
#### Relative URIs
The `uri` parameter can also hold a URI reference (relative URI). In such cases, this URI reference is relative to the URI of the MVS file itself (e.g. if the MVS file is available from `https://example.org/spanish/inquisition/expectations.mvsj`, then the relative URI `./annotations.json` is equivalent to `https://example.org/spanish/inquisition/annotations.json`). This is however not applicable in all cases (e.g. the MVS tree can be constructed ad-hoc within a web application, therefore it has no URI; or the MVS file is loaded from a local disk using drag&drop, therefore the relative location is not accessible by the browser).
### From source
The MVS annotations can in fact be stored within the same mmCIF file from which the structure coordinates are loaded. To reference these annotations, we can use `color_from_source`, `label_from_source`, `tooltip_from_source`, and `component_from_source` nodes. Example:
```txt
- representation {type: "cartoon"}
- color_from_source {schema: "residue_range", block_header: "annotation", category_name: "coloring"}
```
## Annotation schemas
The `schema` parameter of all `*_from_uri` and `*_from_source` nodes specifies the MVS annotation schema, i.e. a set of fields used to select a substructure. In the example above we are using `residue_range` schema, which uses columns `label_entity_id`, `label_asym_id`, `beg_label_seq_id`, and `end_label_seq_id`. (We didn't provide values for `label_entity_id`, so it is not taken into account even though the schema supports it).
Table of selector field names supported by individual MVS annotation schemas:
|Field \ Schema|whole_structure|entity|chain|residue|residue_range|atom|auth_chain|auth_residue|auth_residue_range|auth_atom|all_atomic|
|:------------------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| label_entity_id | | X | X | X | X | X | | | | | X |
| label_asym_id | | | X | X | X | X | | | | | X |
| label_seq_id | | | | X | | X | | | | | X |
| beg_label_seq_id | | | | | X | | | | | | X |
| end_label_seq_id | | | | | X | | | | | | X |
| label_atom_id | | | | | | X | | | | | X |
| auth_asym_id | | | | | | | X | X | X | X | X |
| auth_seq_id | | | | | | | | X | | X | X |
| pdbx_PDB_ins_code | | | | | | | | X | | X | X |
| beg_auth_seq_id | | | | | | | | | X | | X |
| end_auth_seq_id | | | | | | | | | X | | X |
| auth_atom_id | | | | | | | | | | X | X |
| type_symbol | | | | | | X | | | | X | X |
| atom_id | | | | | | X | | | | X | X |
| atom_index | | | | | | X | | | | X | X |
To include all selector field names that are present in the annotation, one can use `"schema": "all_atomic"` (we could use it in the example above and the result would be the same). In future versions of MVS, non-atomic schemas might be added, to select parts of structures that are not composed of atoms, e.g. coarse models or geometric primitives.
## `group_id` field
The `group_id` field is a special field supported by all MVS annotation schemas. It does not change the sets of atoms selected by individual rows but instead groups annotation rows together to create more complex selections. This is useful when adding labels to our visualization.
The following example (when applied via `label_from_uri` node) will create 7 separate labels, each bound to a single residue:
```cif
data_annotation
loop_
_labels.label_asym_id
_labels.label_seq_id
_labels.color
_labels.label
A 100 pink 'Substrate binding site'
A 150 pink 'Substrate binding site'
A 170 pink 'Substrate binding site'
A 200 blue 'Inhibitor binding site'
A 220 blue 'Inhibitor binding site'
A 300 lime 'Glycosylation site'
A 330 lime 'Glycosylation site'
```
On the other hand, the next example will only create 4 labels ("Substrate binding site" label bound to residues 100, 150, and 170; "Inhibitor binding site" label bound to residues 200 and 220; "Glycosylation site" label bound to residue 300; and "Glycosylation site" label bound to residue 330):
```cif
data_annotation
loop_
_labels.group_id
_labels.label_asym_id
_labels.label_seq_id
_labels.color
_labels.label
1 A 100 pink 'Substrate binding site'
1 A 150 pink 'Substrate binding site'
1 A 170 pink 'Substrate binding site'
2 A 200 blue 'Inhibitor binding site'
2 A 220 blue 'Inhibitor binding site'
. A 300 lime 'Glycosylation site'
. A 330 lime 'Glycosylation site'
```
Note: Annotation rows with empty `group_id` field (`.` in CIF, ommitted field or `null` in JSON) are always treated as separate groups.
Note 2: `group_id` field has no effect on colors, tooltips, components. It only makes any difference for labels.

View File

@@ -1,71 +0,0 @@
# MVS camera settings
Camera position and orientation in MVS views can be adjusted in two ways: using a `camera` node or a `focus` node. Global attributes of the MVS view unrelated to camera positioning can be adjusted via a `canvas` node.
## `camera` node
This node instructs to directly set the camera position and orientation. This is done by passing `target`, `position`, and optional `up` vector. The `camera` node is placed as a child of the `root` node (see [MVS tree schema](./mvs-tree-schema.md#camera)).
However, if the `target` and `position` vectors were interpreted directly, the resulting view would wildly depend on the camera field of view (FOV). For example, assume we have a sphere with center in the point [0,0,0] and radius 10 Angstroms, and we set `target=[0,0,0]` and `position=[0,0,20]`. With a camera with vertical FOV=90&deg;, the sphere will fit into the camera's view nicely, with some margin above and under the sphere. But with a camera with vertical FOV=30&deg;, the top and bottom of sphere will be cropped. To avoid these differences, MVS always uses position of a "reference camera" instead of the real camera position.
We define the "reference camera" as a camera with such FOV that a sphere with radius *R* viewed from distance 2*R* (from the center of the sphere) will just fit into view (i.e. there will be no margin but the sphere will not be cropped). This happens to be FOV = 2 arcsin(1/2) = 60&deg; for perspective projection, and FOV = 2 arctan(1/2) &approx; 53&deg; for orthographic projection.
When using **perspective** projection, the real camera distance from target and the real camera position can be calculated using these formulas:
$d _\mathrm{adj} = d _\mathrm{ref} \cdot \frac{1}{2 \sin(\alpha/2)}$
$\mathbf{p} _\mathrm{adj} = \mathbf{t} + (\mathbf{p} _\mathrm{ref} - \mathbf{t}) \cdot \frac{1}{2 \sin(\alpha/2)}$
Where $\alpha$ is the vertical FOV of the real camera, $d _\mathrm{ref}$ is the reference camera distance from target, $d _\mathrm{adj}$ is the real (adjusted) camera distance from target, $\mathbf{t}$ is the target position, $\mathbf{p} _\mathrm{ref}$ is the reference camera position (the actual value in the MVS file), and $\mathbf{p} _\mathrm{adj}$ is the real (adjusted) camera position.
When using **orthographic** projection, the formulas are slightly different:
$d _\mathrm{adj} = d _\mathrm{ref} \cdot \frac{1}{2 \tan(\alpha/2)}$
$\mathbf{p} _\mathrm{adj} = \mathbf{t} + (\mathbf{p} _\mathrm{ref} - \mathbf{t}) \cdot \frac{1}{2 \tan(\alpha/2)}$
Using the example above (`target=[0,0,0]` and `position=[0,0,20]`), we can calculate that the real camera position will have to be set to:
- [0, 0, 14.14] for FOV=90&deg; (perspective projection)
- [0, 0, 20] for FOV=60&deg; (perspective projection)
- [0, 0, 38.68] for FOV=30&deg; (perspective projection)
Note that for orthographic projection this adjustment achieves that the resulting view does not depend on the FOV value. For perspective projection, this is not possible and there will always be some "fisheye effect", but still it greatly reduces the dependence on FOV and avoids the too-much-zoomed-in and too-much-zoomed-out views when FOV changes.
The `up` vector describes how the camera should be rotated around the position-target axis, i.e. it is the vector in 3D space that will be point up when projected on the screen. For this, the `up` vector must be perpendicular to the position-target axis. However, the MVS specification does not require that the provided `up` vector be perpendicular. This can be solved by a simple adjustment:
$\mathbf{u} _\mathrm{adj} = \mathrm{normalize} ( ((\mathbf{t}-\mathbf{p}) \times \mathbf{u}) \times (\mathbf{t}-\mathbf{p}) )$
Where $\mathbf{u}$ is the unadjusted up vector (the actual value in the MVS file), $\mathbf{u} _\mathrm{adj}$ is the adjusted up vector, $\mathbf{t}$ is the target position, and $\mathbf{p}$ is the camera position (can be either reference or adjusted camera position, the result will be the same).
If the up vector parameter is not provided, the default value ([0, 1, 0]) will be used (after adjustment).
## `focus` node
The other way to adjust camera is to use a `focus` node. This node is placed as a child of a `component` node and instructs to set focus to the parent component (zoom in). This means that the camera target should be set to the center of the bounding sphere of the component, and the camera position should be set so that the bounding sphere just fits into view (vertically and horizontally).
By default, the camera will be oriented so that the X axis points right, the Y axis points up, and the Z axis points towards the observer. This orientation can be changed using the optional vector parameters `direction` and `up` (see [MVS tree schema](./mvs-tree-schema.md#focus)). The `direction` vector describes the direction from the camera position towards the target position (default [0, 0, -1]). The meaning of the `up` vector is the same as for the `camera` node and the same adjustment applies to it (default [0, 1, 0]).
The reference camera position for a `focus` node can be calculated as follows:
$\mathbf{p} _\mathrm{ref} = \mathbf{t} - \mathrm{normalize}(\mathbf{d}) \cdot 2 r \cdot \max(1, \frac{h}{w})$
Where $\mathbf{t}$ is the target position (center of the bounding sphere of the component), $r$ is the radius of the bounding sphere of the component, $\mathbf{d}$ is the direction vector, $h$ is the height of the viewport, $w$ is the width of the viewport, and $\mathbf{p} _\mathrm{ref}$ is the reference camera position (see explanation above).
Applying the FOV-adjustment formulas from the previous section, we can easily calculate the real position that we have to set to the camera ($\mathbf{p} _\mathrm{adj}$):
For perspective projection: $\mathbf{p} _\mathrm{adj} = \mathbf{t} - \mathrm{normalize}(\mathbf{d}) \cdot \frac{r}{\sin(\alpha/2)} \cdot \max(1, \frac{h}{w})$
For orthographic projection: $\mathbf{p} _\mathrm{adj} = \mathbf{t} - \mathrm{normalize}(\mathbf{d}) \cdot \frac{r}{\tan(\alpha/2)} \cdot \max(1, \frac{h}{w})$
## `canvas` node
Attributes that apply to the MVS view as a whole, but are not related to camera positioning, can be set using a `canvas` node. This node is placed as a child of the `root` node (see [MVS tree schema](./mvs-tree-schema.md#canvas)).
Currently, this only includes one parameter: `background_color`. Its value can be set to either a [X11 color](http://www.w3.org/TR/css3-color/#svg-color) (e.g. `"red"`), or a hexadecimal color code (e.g. `"#FF0011"`). If there is no `canvas` node, the background will be white.

View File

@@ -1,565 +0,0 @@
# MolViewSpec tree schema
(This documentation was auto-generated by `node lib/commonjs/cli/mvs/mvs-print-schema --markdown`)
## `root`
[Root of the tree must be of this kind]
Auxiliary node kind that only appears as the tree root.
Parent: none
Params: none
## `download`
This node instructs to retrieve a data resource.
Parent: `root`
Params:
- **`url: `**`string`
URL of the data resource.
## `parse`
This node instructs to parse a data resource.
Parent: `download`
Params:
- **`format: `**`"mmcif" | "bcif" | "pdb"`
Format of the input data resource.
## `structure`
This node instructs to create a structure from a parsed data resource. "Structure" refers to an internal representation of molecular coordinates without any visual representation.
Parent: `parse`
Params:
- **`type: `**`"model" | "assembly" | "symmetry" | "symmetry_mates"`
Type of structure to be created (`"model"` for original model coordinates, `"assembly"` for assembly structure, `"symmetry"` for a set of crystal unit cells based on Miller indices, `"symmetry_mates"` for a set of asymmetric units within a radius from the original model).
- **`block_header?: `**`string | null`
Header of the CIF block to read coordinates from (only applies when the input data are from CIF or BinaryCIF). If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read coordinates from (only applies when the input data are from CIF or BinaryCIF and `block_header` is `null`).
Default: `0`
- **`model_index?: `**`Integer`
0-based index of model in case the input data contain multiple models.
Default: `0`
- **`assembly_id?: `**`string | null`
Assembly identifier (only applies when `kind` is `"assembly"`). If `null`, the first assembly is selected.
Default: `null`
- **`radius?: `**`number`
Distance (in Angstroms) from the original model in which asymmetric units should be included (only applies when `kind` is `"symmetry_mates"`).
Default: `5`
- **`ijk_min?: `**`[Integer, Integer, Integer]`
Miller indices of the bottom-left unit cell to be included (only applies when `kind` is `"symmetry"`).
Default: `[-1, -1, -1]`
- **`ijk_max?: `**`[Integer, Integer, Integer]`
Miller indices of the top-right unit cell to be included (only applies when `kind` is `"symmetry"`).
Default: `[1, 1, 1]`
## `transform`
This node instructs to rotate and/or translate structure coordinates.
Parent: `structure`
Params:
- **`rotation?: `**`Array<number>`
Rotation matrix (3x3 matrix flattened in column major format (j*3+i indexing), this is equivalent to Fortran-order in numpy). This matrix will multiply the structure coordinates from the left. The default value is the identity matrix (corresponds to no rotation).
Default: `[1, 0, 0, 0, 1, 0, 0, 0, 1]`
- **`translation?: `**`[number, number, number]`
Translation vector, applied to the structure coordinates after rotation. The default value is the zero vector (corresponds to no translation).
Default: `[0, 0, 0]`
## `component`
This node instructs to create a component (i.e. a subset of the parent structure).
Parent: `structure`
Params:
- **`selector: `**`("all" | "polymer" | "protein" | "nucleic" | "branched" | "ligand" | "ion" | "water") | Partial<{ label_entity_id: string, label_asym_id: string, auth_asym_id: string, label_seq_id: Integer, auth_seq_id: Integer, pdbx_PDB_ins_code: string, beg_label_seq_id: Integer, end_label_seq_id: Integer, beg_auth_seq_id: Integer, end_auth_seq_id: Integer, label_atom_id: string, auth_atom_id: string, type_symbol: string, atom_id: Integer, atom_index: Integer }> | Array<Partial<{ label_entity_id: string, label_asym_id: string, auth_asym_id: string, label_seq_id: Integer, auth_seq_id: Integer, pdbx_PDB_ins_code: string, beg_label_seq_id: Integer, end_label_seq_id: Integer, beg_auth_seq_id: Integer, end_auth_seq_id: Integer, label_atom_id: string, auth_atom_id: string, type_symbol: string, atom_id: Integer, atom_index: Integer }>>`
Defines what part of the parent structure should be included in this component.
Default: `"all"`
## `component_from_uri`
This node instructs to create a component defined by an external annotation resource.
Parent: `structure`
Params:
- **`uri: `**`string`
URL of the annotation resource.
- **`format: `**`"cif" | "bcif" | "json"`
Format of the annotation resource.
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"` and `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"component"`
- **`field_values?: `**`Array<string> | null`
List of component identifiers (i.e. values in the field given by `field_name`) which should be included in this component. If `null`, component identifiers are ignored (all annotation rows are included), and `field_name` field can be dropped from the annotation.
Default: `null`
## `component_from_source`
This node instructs to create a component defined by an annotation resource included in the same file this structure was loaded from. Only applicable if the structure was loaded from an mmCIF or BinaryCIF file.
Parent: `structure`
Params:
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from. If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from. If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"component"`
- **`field_values?: `**`Array<string> | null`
List of component identifiers (i.e. values in the field given by `field_name`) which should be included in this component. If `null`, component identifiers are ignored (all annotation rows are included), and `field_name` field can be dropped from the annotation.
Default: `null`
## `representation`
This node instructs to create a visual representation of a component.
Parent: `component` or `component_from_uri` or `component_from_source`
Params:
- **`type: `**`"ball_and_stick" | "cartoon" | "surface"`
Method of visual representation of the component.
## `color`
This node instructs to apply color to a visual representation.
Parent: `representation`
Params:
- **`color: `**`HexColor | ("aliceblue" | "antiquewhite" | "aqua" | "aquamarine" | "azure" | "beige" | "bisque" | "black" | "blanchedalmond" | "blue" | "blueviolet" | "brown" | "burlywood" | "cadetblue" | "chartreuse" | "chocolate" | "coral" | "cornflower" | "cornflowerblue" | "cornsilk" | "crimson" | "cyan" | "darkblue" | "darkcyan" | "darkgoldenrod" | "darkgray" | "darkgreen" | "darkgrey" | "darkkhaki" | "darkmagenta" | "darkolivegreen" | "darkorange" | "darkorchid" | "darkred" | "darksalmon" | "darkseagreen" | "darkslateblue" | "darkslategray" | "darkslategrey" | "darkturquoise" | "darkviolet" | "deeppink" | "deepskyblue" | "dimgray" | "dimgrey" | "dodgerblue" | "firebrick" | "floralwhite" | "forestgreen" | "fuchsia" | "gainsboro" | "ghostwhite" | "gold" | "goldenrod" | "gray" | "green" | "greenyellow" | "grey" | "honeydew" | "hotpink" | "indianred" | "indigo" | "ivory" | "khaki" | "laserlemon" | "lavender" | "lavenderblush" | "lawngreen" | "lemonchiffon" | "lightblue" | "lightcoral" | "lightcyan" | "lightgoldenrod" | "lightgoldenrodyellow" | "lightgray" | "lightgreen" | "lightgrey" | "lightpink" | "lightsalmon" | "lightseagreen" | "lightskyblue" | "lightslategray" | "lightslategrey" | "lightsteelblue" | "lightyellow" | "lime" | "limegreen" | "linen" | "magenta" | "maroon" | "maroon2" | "maroon3" | "mediumaquamarine" | "mediumblue" | "mediumorchid" | "mediumpurple" | "mediumseagreen" | "mediumslateblue" | "mediumspringgreen" | "mediumturquoise" | "mediumvioletred" | "midnightblue" | "mintcream" | "mistyrose" | "moccasin" | "navajowhite" | "navy" | "oldlace" | "olive" | "olivedrab" | "orange" | "orangered" | "orchid" | "palegoldenrod" | "palegreen" | "paleturquoise" | "palevioletred" | "papayawhip" | "peachpuff" | "peru" | "pink" | "plum" | "powderblue" | "purple" | "purple2" | "purple3" | "rebeccapurple" | "red" | "rosybrown" | "royalblue" | "saddlebrown" | "salmon" | "sandybrown" | "seagreen" | "seashell" | "sienna" | "silver" | "skyblue" | "slateblue" | "slategray" | "slategrey" | "snow" | "springgreen" | "steelblue" | "tan" | "teal" | "thistle" | "tomato" | "turquoise" | "violet" | "wheat" | "white" | "whitesmoke" | "yellow" | "yellowgreen")`
Color to apply to the representation. Can be either an X11 color name (e.g. `"red"`) or a hexadecimal code (e.g. `"#FF0011"`).
- **`selector?: `**`("all" | "polymer" | "protein" | "nucleic" | "branched" | "ligand" | "ion" | "water") | Partial<{ label_entity_id: string, label_asym_id: string, auth_asym_id: string, label_seq_id: Integer, auth_seq_id: Integer, pdbx_PDB_ins_code: string, beg_label_seq_id: Integer, end_label_seq_id: Integer, beg_auth_seq_id: Integer, end_auth_seq_id: Integer, label_atom_id: string, auth_atom_id: string, type_symbol: string, atom_id: Integer, atom_index: Integer }> | Array<Partial<{ label_entity_id: string, label_asym_id: string, auth_asym_id: string, label_seq_id: Integer, auth_seq_id: Integer, pdbx_PDB_ins_code: string, beg_label_seq_id: Integer, end_label_seq_id: Integer, beg_auth_seq_id: Integer, end_auth_seq_id: Integer, label_atom_id: string, auth_atom_id: string, type_symbol: string, atom_id: Integer, atom_index: Integer }>>`
Defines to what part of the representation this color should be applied.
Default: `"all"`
## `color_from_uri`
This node instructs to apply colors to a visual representation. The colors are defined by an external annotation resource.
Parent: `representation`
Params:
- **`uri: `**`string`
URL of the annotation resource.
- **`format: `**`"cif" | "bcif" | "json"`
Format of the annotation resource.
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"` and `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"color"`
## `color_from_source`
This node instructs to apply colors to a visual representation. The colors are defined by an annotation resource included in the same file this structure was loaded from. Only applicable if the structure was loaded from an mmCIF or BinaryCIF file.
Parent: `representation`
Params:
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from. If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from. If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"color"`
## `label`
This node instructs to add a label (textual visual representation) to a component.
Parent: `component` or `component_from_uri` or `component_from_source`
Params:
- **`text: `**`string`
Content of the shown label.
## `label_from_uri`
This node instructs to add labels (textual visual representations) to parts of a structure. The labels are defined by an external annotation resource.
Parent: `structure`
Params:
- **`uri: `**`string`
URL of the annotation resource.
- **`format: `**`"cif" | "bcif" | "json"`
Format of the annotation resource.
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"` and `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"label"`
## `label_from_source`
This node instructs to add labels (textual visual representations) to parts of a structure. The labels are defined by an annotation resource included in the same file this structure was loaded from. Only applicable if the structure was loaded from an mmCIF or BinaryCIF file.
Parent: `structure`
Params:
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from. If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from. If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"label"`
## `tooltip`
This node instructs to add a tooltip to a component. "Tooltip" is a text which is not a part of the visualization but should be presented to the users when they interact with the component (typically, the tooltip will be shown somewhere on the screen when the user hovers over a visual representation of the component).
Parent: `component` or `component_from_uri` or `component_from_source`
Params:
- **`text: `**`string`
Content of the shown tooltip.
## `tooltip_from_uri`
This node instructs to add tooltips to parts of a structure. The tooltips are defined by an external annotation resource.
Parent: `structure`
Params:
- **`uri: `**`string`
URL of the annotation resource.
- **`format: `**`"cif" | "bcif" | "json"`
Format of the annotation resource.
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `format` is `"cif"` or `"bcif"` and `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from (only applies when `format` is `"cif"` or `"bcif"`). If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"tooltip"`
## `tooltip_from_source`
This node instructs to add tooltips to parts of a structure. The tooltips are defined by an annotation resource included in the same file this structure was loaded from. Only applicable if the structure was loaded from an mmCIF or BinaryCIF file.
Parent: `structure`
Params:
- **`schema: `**`"whole_structure" | "entity" | "chain" | "auth_chain" | "residue" | "auth_residue" | "residue_range" | "auth_residue_range" | "atom" | "auth_atom" | "all_atomic"`
Annotation schema defines what fields in the annotation will be taken into account.
- **`block_header?: `**`string | null`
Header of the CIF block to read annotation from. If `null`, block is selected based on `block_index`.
Default: `null`
- **`block_index?: `**`Integer`
0-based index of the CIF block to read annotation from (only applies when `block_header` is `null`).
Default: `0`
- **`category_name?: `**`string | null`
Name of the CIF category to read annotation from. If `null`, the first category in the block is used.
Default: `null`
- **`field_name?: `**`string`
Name of the column in CIF or field name (key) in JSON that contains the dependent variable (color/label/tooltip/component_id...).
Default: `"tooltip"`
## `focus`
This node instructs to set the camera focus to a component (zoom in).
Parent: `component` or `component_from_uri` or `component_from_source`
Params:
- **`direction?: `**`[number, number, number]`
Vector describing the direction of the view (camera position -> focused target).
Default: `[0, 0, -1]`
- **`up?: `**`[number, number, number]`
Vector which will be aligned with the screen Y axis.
Default: `[0, 1, 0]`
## `camera`
This node instructs to set the camera position and orientation.
Parent: `root`
Params:
- **`target: `**`[number, number, number]`
Coordinates of the point in space at which the camera is pointing.
- **`position: `**`[number, number, number]`
Coordinates of the camera.
- **`up?: `**`[number, number, number]`
Vector which will be aligned with the screen Y axis.
Default: `[0, 1, 0]`
## `canvas`
This node sets canvas properties.
Parent: `root`
Params:
- **`background_color: `**`HexColor | ("aliceblue" | "antiquewhite" | "aqua" | "aquamarine" | "azure" | "beige" | "bisque" | "black" | "blanchedalmond" | "blue" | "blueviolet" | "brown" | "burlywood" | "cadetblue" | "chartreuse" | "chocolate" | "coral" | "cornflower" | "cornflowerblue" | "cornsilk" | "crimson" | "cyan" | "darkblue" | "darkcyan" | "darkgoldenrod" | "darkgray" | "darkgreen" | "darkgrey" | "darkkhaki" | "darkmagenta" | "darkolivegreen" | "darkorange" | "darkorchid" | "darkred" | "darksalmon" | "darkseagreen" | "darkslateblue" | "darkslategray" | "darkslategrey" | "darkturquoise" | "darkviolet" | "deeppink" | "deepskyblue" | "dimgray" | "dimgrey" | "dodgerblue" | "firebrick" | "floralwhite" | "forestgreen" | "fuchsia" | "gainsboro" | "ghostwhite" | "gold" | "goldenrod" | "gray" | "green" | "greenyellow" | "grey" | "honeydew" | "hotpink" | "indianred" | "indigo" | "ivory" | "khaki" | "laserlemon" | "lavender" | "lavenderblush" | "lawngreen" | "lemonchiffon" | "lightblue" | "lightcoral" | "lightcyan" | "lightgoldenrod" | "lightgoldenrodyellow" | "lightgray" | "lightgreen" | "lightgrey" | "lightpink" | "lightsalmon" | "lightseagreen" | "lightskyblue" | "lightslategray" | "lightslategrey" | "lightsteelblue" | "lightyellow" | "lime" | "limegreen" | "linen" | "magenta" | "maroon" | "maroon2" | "maroon3" | "mediumaquamarine" | "mediumblue" | "mediumorchid" | "mediumpurple" | "mediumseagreen" | "mediumslateblue" | "mediumspringgreen" | "mediumturquoise" | "mediumvioletred" | "midnightblue" | "mintcream" | "mistyrose" | "moccasin" | "navajowhite" | "navy" | "oldlace" | "olive" | "olivedrab" | "orange" | "orangered" | "orchid" | "palegoldenrod" | "palegreen" | "paleturquoise" | "palevioletred" | "papayawhip" | "peachpuff" | "peru" | "pink" | "plum" | "powderblue" | "purple" | "purple2" | "purple3" | "rebeccapurple" | "red" | "rosybrown" | "royalblue" | "saddlebrown" | "salmon" | "sandybrown" | "seagreen" | "seashell" | "sienna" | "silver" | "skyblue" | "slateblue" | "slategray" | "slategrey" | "snow" | "springgreen" | "steelblue" | "tan" | "teal" | "thistle" | "tomato" | "turquoise" | "violet" | "wheat" | "white" | "whitesmoke" | "yellow" | "yellowgreen")`
Color of the canvas background. Can be either an X11 color name (e.g. `"red"`) or a hexadecimal code (e.g. `"#FF0011"`).

View File

@@ -1,56 +0,0 @@
# MVS selectors
Selectors are used in MVS to define substructures (components) and apply colors, labels, or tooltips to them. MVS nodes that take a `selector` parameter are `component` (creates a component from the parent `structure` node) and `color` (applies coloring to a part of the parent `representation` node).
There are three kinds of selectors:
- **Static selector** is a string that selects a part of the structure based on entity type. The supported static selectors are these:
`"all", "polymer", "protein", "nucleic", "branched", "ligand", "ion", "water"`
- **Component expression** is an object that selects a set of atoms based on their properties like chain identifier, residue number, or type symbol. The type of a component expression object is:
```ts
{
label_entity_id?: str, // Entity identifier
label_asym_id?: str, // Chain identifier in label_* numbering
auth_asym_id?: str, // Chain identifier in auth_* numbering
label_seq_id?: int, // Residue number in label_* numbering
auth_seq_id?: int, // Residue number in auth_* numbering
pdbx_PDB_ins_code?: str, // PDB insertion code
beg_label_seq_id?: int, // Minimum label_seq_id (inclusive), leave blank to start from the beginning of the chain
end_label_seq_id?: int, // Maximum label_seq_id (inclusive), leave blank to go to the end of the chain
beg_auth_seq_id?: int, // Minimum auth_seq_id (inclusive), leave blank to start from the beginning of the chain
end_auth_seq_id?: int, // Maximum auth_seq_id (inclusive), leave blank to go to the end of the chain
label_atom_id?: str, // Atom name like 'CA', 'N', 'O', in label_* numbering
auth_atom_id?: str, // Atom name like 'CA', 'N', 'O', in auth_* numbering
type_symbol?: str, // Element symbol like 'H', 'HE', 'LI', 'BE'
atom_id?: int, // Unique atom identifier (_atom_site.id)
atom_index?: int, // 0-based index of the atom in the source data
}
```
A component expression can include any combination of the fields. An expression with multiple fields selects atoms that fulfill all fields at the same time. Examples:
```ts
// Select whole chain A
selector: { label_asym_id: 'A' }
// Select residues 100 to 200 (inclusive) in chain B
selector: { label_asym_id: 'B', beg_label_seq_id: 100, end_label_seq_id: 200 }
// Select C-alpha atoms in residue 100 (using auth_* numbering) of any chain
selector: { auth_seq_id: 100, type_symbol: 'C', auth_atom_id: 'CA' }
```
- **Union component expression** is an array of simple component expressions. A union component expression is interpreted as set union, i.e. it selects all atoms that fulfill at least one of the expressions in the array. Example:
```ts
// Select chains A, B, and C
selector: [{ label_asym_id: 'A' }, { label_asym_id: 'B' }, { label_asym_id: 'C' }]
// Select residues up to 100 (inclusive) in chain A plus all magnesium atoms
selector: [{ label_asym_id: 'A', end_label_seq_id: 100 }, { type_symbol: 'MG' }]
```
An alternative to using selectors is using [MVS annotations](./annotations.md). This means defining the selections in a separate file and referencing them from the MVS file.

58
docs/mkdocs.yml Normal file
View File

@@ -0,0 +1,58 @@
site_name: Mol* Developer Documentation
theme:
name: material
# 404 page
static_templates:
- 404.html
# Necessary for search to work properly
include_search_page: false
search_index_only: true
# Default values, taken from mkdocs_theme.yml
language: en
font:
text: Roboto
code: Roboto Mono
favicon: assets/favicon.png
icon:
logo: logo
markdown_extensions:
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.arithmatex:
generic: true
# Scripts for rendering Latex equations (in addition to pymdownx.arithmatex):
extra_javascript:
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
nav:
- 'index.md'
- Plugin:
- Creating Instance: 'plugin/instance.md'
- Examples: plugin/examples.md
- Selections: 'plugin/selections.md'
- Viewer State: 'plugin/viewer-state.md'
- Data State: 'plugin/data-state.md'
- File Formats: 'plugin/file-formats.md'
- CIF Schemas: 'plugin/cif-schemas.md'
- State Transforms:
- Custom Trajectory: 'plugin/transforms/custom-trajectory.md'
- Data Access Tools:
- 'data-access-tools/model-server.md'
- Volume Server:
- Overview: 'data-access-tools/volume-server/index.md'
- Examples: 'data-access-tools/volume-server/examples.md'
- How it Works: 'data-access-tools/volume-server/how-it-works.md'
- Data Format: 'data-access-tools/volume-server/response-data-format.md'
- 'data-access-tools/plugin-state-server.md'
- 'data-access-tools/convert-to-bcif.md'
- 'data-access-tools/create-ccd-table.md'
- 'data-access-tools/extract-ccd-ions.md'
- Extensions:
- MolViewSpec: 'extensions/mvs/index.md'
- wwPDB StructConn: 'extensions/struct-conn.md'
- Misc:
- Interesting PDB entries: misc/interesting-pdb-entries.md
repo_url: https://github.com/molstar/docs

View File

@@ -1,69 +0,0 @@
Model Server
============
Model Server is a tool for preprocessing and querying macromolecular structure data.
Installing and Running
=====================
Requires nodejs 8+.
## From GitHub
```
git clone https://github.com/molstar/molstar
npm install
```
Afterwards, build the project source:
```
npm run build-tsc
```
and run the server by
```
node lib/commonjs/servers/model/server/server
```
## From NPM
```
npm install --production molstar
./model-server
```
(or ``node node_modules\.bin\model-server`` in Windows).
The NPM package contains all the tools mentioned here as "binaries":
- ``model-server``
- ``model-server-query``
- ``model-server-preprocess``
### Production use
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
### Memory issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
## Preprocessor
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/commonjs/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
## Local Mode
The server can be run in local/file based mode using ``node lib/commonjs/servers/model/query`` (``model-server-query`` binary from the NPM package).
Custom Properties
=================
This feature is still in development.
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.

View File

@@ -1,738 +0,0 @@
# Mol* Plugin State Transformer Reference
* [build-in.root](#build-in-root)
* [ms-plugin.download](#ms-plugin-download)
* [ms-plugin.read-file](#ms-plugin-read-file)
* [ms-plugin.parse-cif](#ms-plugin-parse-cif)
* [ms-plugin.parse-ccp4](#ms-plugin-parse-ccp4)
* [ms-plugin.parse-dsn6](#ms-plugin-parse-dsn6)
* [ms-plugin.trajectory-from-mmcif](#ms-plugin-trajectory-from-mmcif)
* [ms-plugin.trajectory-from-pdb](#ms-plugin-trajectory-from-pdb)
* [ms-plugin.model-from-trajectory](#ms-plugin-model-from-trajectory)
* [ms-plugin.structure-from-model](#ms-plugin-structure-from-model)
* [ms-plugin.structure-assembly-from-model](#ms-plugin-structure-assembly-from-model)
* [ms-plugin.structure-symmetry-from-model](#ms-plugin-structure-symmetry-from-model)
* [ms-plugin.structure-selection](#ms-plugin-structure-selection)
* [ms-plugin.structure-complex-element](#ms-plugin-structure-complex-element)
* [ms-plugin.custom-model-properties](#ms-plugin-custom-model-properties)
* [ms-plugin.volume-from-ccp4](#ms-plugin-volume-from-ccp4)
* [ms-plugin.volume-from-dsn6](#ms-plugin-volume-from-dsn6)
* [ms-plugin.representation-highlight-loci](#ms-plugin-representation-highlight-loci)
* [ms-plugin.representation-select-loci](#ms-plugin-representation-select-loci)
* [ms-plugin.default-loci-label-provider](#ms-plugin-default-loci-label-provider)
* [ms-plugin.structure-representation-3d](#ms-plugin-structure-representation-3d)
* [ms-plugin.explode-structure-representation-3d](#ms-plugin-explode-structure-representation-3d)
* [ms-plugin.volume-representation-3d](#ms-plugin-volume-representation-3d)
* [ms-plugin.focus-loci-on-select](#ms-plugin-focus-loci-on-select)
* [ms-plugin.pdbe-structure-quality-report-prop](#ms-plugin-pdbe-structure-quality-report-prop)
* [ms-plugin.rcsb-assembly-symmetry-prop](#ms-plugin-rcsb-assembly-symmetry-prop)
* [ms-plugin.structure-animation](#ms-plugin-structure-animation)
* [ms-plugin.scene-labels](#ms-plugin-scene-labels)
----------------------------
## <a name="build-in-root"></a>build-in.root :: () -> ()
*For internal use.*
----------------------------
## <a name="ms-plugin-download"></a>ms-plugin.download :: Root -> String | Binary
*Download string or binary data from the specified URL*
### Parameters
- **url**: String *(Resource URL. Must be the same domain or support CORS.)*
- **label**?: String
- **isBinary**?: true/false *(If true, download data as binary (string otherwise))*
### Default Parameters
```js
{
"url": "https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif"
}
```
----------------------------
## <a name="ms-plugin-read-file"></a>ms-plugin.read-file :: Root -> String | Binary
*Read string or binary data from the specified file*
### Parameters
- **file**: JavaScript File Handle
- **label**?: String
- **isBinary**?: true/false *(If true, open file as as binary (string otherwise))*
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-parse-cif"></a>ms-plugin.parse-cif :: String | Binary -> Cif
*Parse CIF from String or Binary data*
----------------------------
## <a name="ms-plugin-parse-ccp4"></a>ms-plugin.parse-ccp4 :: Binary -> Ccp4
*Parse CCP4/MRC/MAP from Binary data*
----------------------------
## <a name="ms-plugin-parse-dsn6"></a>ms-plugin.parse-dsn6 :: Binary -> Dsn6
*Parse CCP4/BRIX from Binary data*
----------------------------
## <a name="ms-plugin-trajectory-from-mmcif"></a>ms-plugin.trajectory-from-mmcif :: Cif -> Trajectory
*Identify and create all separate models in the specified CIF data block*
### Parameters
- **blockHeader**?: String *(Header of the block to parse. If none is specifed, the 1st data block in the file is used.)*
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-trajectory-from-pdb"></a>ms-plugin.trajectory-from-pdb :: String -> Trajectory
----------------------------
## <a name="ms-plugin-model-from-trajectory"></a>ms-plugin.model-from-trajectory :: Trajectory -> Model
*Create a molecular structure from the specified model.*
### Parameters
- **modelIndex**: Numeric value *(Zero-based index of the model)*
### Default Parameters
```js
{
"modelIndex": 0
}
```
----------------------------
## <a name="ms-plugin-structure-from-model"></a>ms-plugin.structure-from-model :: Model -> Structure
*Create a molecular structure from the specified model.*
----------------------------
## <a name="ms-plugin-structure-assembly-from-model"></a>ms-plugin.structure-assembly-from-model :: Model -> Structure
*Create a molecular structure assembly.*
### Parameters
- **id**?: String *(Assembly Id. Value 'deposited' can be used to specify deposited asymmetric unit.)*
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-structure-symmetry-from-model"></a>ms-plugin.structure-symmetry-from-model :: Model -> Structure
*Create a molecular structure symmetry.*
### Parameters
- **ijkMin**: 3D vector [x, y, z]
- **ijkMax**: 3D vector [x, y, z]
### Default Parameters
```js
{
"ijkMin": [
-1,
-1,
-1
],
"ijkMax": [
1,
1,
1
]
}
```
----------------------------
## <a name="ms-plugin-structure-selection"></a>ms-plugin.structure-selection :: Structure -> Structure
*Create a molecular structure from the specified query expression.*
### Parameters
- **query**: Value
- **label**?: String
### Default Parameters
```js
{}
```
----------------------------
## <a name="ms-plugin-structure-complex-element"></a>ms-plugin.structure-complex-element :: Structure -> Structure
*Create a molecular structure from the specified model.*
### Parameters
- **type**: One of 'atomic-sequence', 'water', 'atomic-het', 'spheres'
### Default Parameters
```js
{
"type": "atomic-sequence"
}
```
----------------------------
## <a name="ms-plugin-custom-model-properties"></a>ms-plugin.custom-model-properties :: Model -> Model
### Parameters
- **properties**: Array of *(A list of property descriptor ids.)*
### Default Parameters
```js
{
"properties": []
}
```
----------------------------
## <a name="ms-plugin-volume-from-ccp4"></a>ms-plugin.volume-from-ccp4 :: Ccp4 -> Data
*Create Volume from CCP4/MRC/MAP data*
### Parameters
- **voxelSize**: 3D vector [x, y, z]
### Default Parameters
```js
{
"voxelSize": [
1,
1,
1
]
}
```
----------------------------
## <a name="ms-plugin-volume-from-dsn6"></a>ms-plugin.volume-from-dsn6 :: Dsn6 -> Data
*Create Volume from DSN6/BRIX data*
### Parameters
- **voxelSize**: 3D vector [x, y, z]
### Default Parameters
```js
{
"voxelSize": [
1,
1,
1
]
}
```
----------------------------
## <a name="ms-plugin-representation-highlight-loci"></a>ms-plugin.representation-highlight-loci :: Root -> Behavior
----------------------------
## <a name="ms-plugin-representation-select-loci"></a>ms-plugin.representation-select-loci :: Root -> Behavior
----------------------------
## <a name="ms-plugin-default-loci-label-provider"></a>ms-plugin.default-loci-label-provider :: Root -> Behavior
----------------------------
## <a name="ms-plugin-structure-representation-3d"></a>ms-plugin.structure-representation-3d :: Structure -> Representation3D
### Parameters
- **type**: Object { name: string, params: object } where name+params are:
- **cartoon**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **sizeFactor**: Numeric value
- **linearSegments**: Numeric value
- **radialSegments**: Numeric value
- **aspectRatio**: Numeric value
- **arrowFactor**: Numeric value
- **visuals**: Array of 'polymer-trace', 'polymer-gap', 'nucleotide-block', 'direction-wedge'
- **ball-and-stick**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **sizeFactor**: Numeric value
- **detail**: Numeric value
- **linkScale**: Numeric value
- **linkSpacing**: Numeric value
- **radialSegments**: Numeric value
- **sizeAspectRatio**: Numeric value
- **visuals**: Array of 'element-sphere', 'intra-link', 'inter-link'
- **carbohydrate**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **detail**: Numeric value
- **sizeFactor**: Numeric value
- **linkScale**: Numeric value
- **linkSpacing**: Numeric value
- **radialSegments**: Numeric value
- **linkSizeFactor**: Numeric value
- **visuals**: Array of 'carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'
- **distance-restraint**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **linkScale**: Numeric value
- **linkSpacing**: Numeric value
- **radialSegments**: Numeric value
- **sizeFactor**: Numeric value
- **molecular-surface**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **resolution**: Numeric value
- **radiusOffset**: Numeric value
- **smoothness**: Numeric value
- **useGpu**: true/false
- **ignoreCache**: true/false
- **sizeFactor**: Numeric value
- **lineSizeAttenuation**: true/false
- **visuals**: Array of 'gaussian-surface', 'gaussian-wireframe'
- **molecular-volume**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **isoValueNorm**: Numeric value *(Normalized Isolevel Value)*
- **renderMode**: One of 'isosurface', 'volume'
- **controlPoints**: A list of 2d vectors [xi, yi][]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **resolution**: Numeric value
- **radiusOffset**: Numeric value
- **smoothness**: Numeric value
- **point**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **sizeFactor**: Numeric value
- **pointSizeAttenuation**: true/false
- **pointFilledCircle**: true/false
- **pointEdgeBleach**: Numeric value
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **spacefill**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
- **sizeFactor**: Numeric value
- **detail**: Numeric value
- **colorTheme**: Object { name: string, params: object } where name+params are:
- **carbohydrate-symbol**:
Object with:
- **chain-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **cross-link**:
Object with:
- **domain**: Interval [min, max]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-symbol**:
Object with:
- **molecule-type**:
Object with:
- **polymer-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **polymer-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **residue-name**:
Object with:
- **secondary-structure**:
Object with:
- **sequence-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **shape-group**:
Object with:
- **unit-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **uniform**:
Object with:
- **value**: Color as 0xrrggbb
- **sizeTheme**: Object { name: string, params: object } where name+params are:
- **physical**:
Object with:
- **shape-group**:
Object with:
- **uniform**:
Object with:
- **value**: Numeric value
### Default Parameters
```js
{
"type": {
"name": "cartoon",
"params": {
"alpha": 1,
"useFog": true,
"highlightColor": 16737945,
"selectColor": 3407641,
"quality": "auto",
"doubleSided": false,
"flipSided": false,
"flatShaded": false,
"unitKinds": [
"atomic",
"spheres"
],
"sizeFactor": 0.2,
"linearSegments": 8,
"radialSegments": 16,
"aspectRatio": 5,
"arrowFactor": 1.5,
"visuals": [
"polymer-trace"
]
}
},
"colorTheme": {
"name": "polymer-id",
"params": {
"list": "RedYellowBlue"
}
},
"sizeTheme": {
"name": "uniform",
"params": {
"value": 1
}
}
}
```
----------------------------
## <a name="ms-plugin-explode-structure-representation-3d"></a>ms-plugin.explode-structure-representation-3d :: Representation3D -> Obj
### Parameters
- **t**: Numeric value
### Default Parameters
```js
{
"t": 0
}
```
----------------------------
## <a name="ms-plugin-volume-representation-3d"></a>ms-plugin.volume-representation-3d :: Data -> Representation3D
### Parameters
- **type**: Object { name: string, params: object } where name+params are:
- **isosurface**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **doubleSided**: true/false
- **flipSided**: true/false
- **flatShaded**: true/false
- **isoValue**: - **absolute**: Numeric value
- **relative**: Numeric value
- **sizeFactor**: Numeric value
- **lineSizeAttenuation**: true/false
- **visuals**: Array of 'solid', 'wireframe'
- **direct-volume**:
Object with:
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **isoValueNorm**: Numeric value *(Normalized Isolevel Value)*
- **renderMode**: One of 'isosurface', 'volume'
- **controlPoints**: A list of 2d vectors [xi, yi][]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **colorTheme**: Object { name: string, params: object } where name+params are:
- **carbohydrate-symbol**:
Object with:
- **chain-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **cross-link**:
Object with:
- **domain**: Interval [min, max]
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **element-symbol**:
Object with:
- **molecule-type**:
Object with:
- **polymer-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **polymer-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **residue-name**:
Object with:
- **secondary-structure**:
Object with:
- **sequence-id**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **shape-group**:
Object with:
- **unit-index**:
Object with:
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
- **uniform**:
Object with:
- **value**: Color as 0xrrggbb
- **sizeTheme**: Object { name: string, params: object } where name+params are:
- **physical**:
Object with:
- **shape-group**:
Object with:
- **uniform**:
Object with:
- **value**: Numeric value
### Default Parameters
```js
{
"type": {
"name": "isosurface",
"params": {
"alpha": 1,
"useFog": true,
"highlightColor": 16737945,
"selectColor": 3407641,
"quality": "auto",
"doubleSided": false,
"flipSided": false,
"flatShaded": false,
"isoValue": {
"kind": "relative",
"stats": {
"min": 0,
"max": 0,
"mean": 0,
"sigma": 0
},
"relativeValue": 2
},
"sizeFactor": 1,
"lineSizeAttenuation": false,
"visuals": [
"solid"
]
}
},
"colorTheme": {
"name": "uniform",
"params": {
"value": 13421772
}
},
"sizeTheme": {
"name": "uniform",
"params": {
"value": 1
}
}
}
```
----------------------------
## <a name="ms-plugin-focus-loci-on-select"></a>ms-plugin.focus-loci-on-select :: Root -> Behavior
### Parameters
- **minRadius**: Numeric value
- **extraRadius**: Numeric value *(Value added to the boundning sphere radius of the Loci.)*
### Default Parameters
```js
{
"minRadius": 10,
"extraRadius": 4
}
```
----------------------------
## <a name="ms-plugin-pdbe-structure-quality-report-prop"></a>ms-plugin.pdbe-structure-quality-report-prop :: Root -> Behavior
### Parameters
- **autoAttach**: true/false
### Default Parameters
```js
{
"autoAttach": false
}
```
----------------------------
## <a name="ms-plugin-rcsb-assembly-symmetry-prop"></a>ms-plugin.rcsb-assembly-symmetry-prop :: Root -> Behavior
### Parameters
- **autoAttach**: true/false
### Default Parameters
```js
{
"autoAttach": false
}
```
----------------------------
## <a name="ms-plugin-structure-animation"></a>ms-plugin.structure-animation :: Root -> Behavior
### Parameters
- **rotate**: true/false
- **rotateValue**: Numeric value
- **explode**: true/false
- **explodeValue**: Numeric value
### Default Parameters
```js
{
"rotate": false,
"rotateValue": 0,
"explode": false,
"explodeValue": 0
}
```
----------------------------
## <a name="ms-plugin-scene-labels"></a>ms-plugin.scene-labels :: Root -> Behavior
### Parameters
- **alpha**: Numeric value
- **useFog**: true/false
- **highlightColor**: Color as 0xrrggbb
- **selectColor**: Color as 0xrrggbb
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
- **fontFamily**: One of 'sans-serif', 'monospace', 'serif', 'cursive'
- **fontQuality**: One of '0', '1', '2', '3', '4'
- **fontStyle**: One of 'normal', 'italic', 'oblique'
- **fontVariant**: One of 'normal', 'small-caps'
- **fontWeight**: One of 'normal', 'bold'
- **sizeFactor**: Numeric value
- **borderWidth**: Numeric value
- **borderColor**: Color as 0xrrggbb
- **offsetX**: Numeric value
- **offsetY**: Numeric value
- **offsetZ**: Numeric value
- **background**: true/false
- **backgroundMargin**: Numeric value
- **backgroundColor**: Color as 0xrrggbb
- **backgroundOpacity**: Numeric value
- **attachment**: One of 'bottom-left', 'bottom-center', 'bottom-right', 'middle-left', 'middle-center', 'middle-right', 'top-left', 'top-center', 'top-right'
- **levels**: Array of 'structure', 'polymer', 'ligand'
### Default Parameters
```js
{
"alpha": 1,
"useFog": true,
"highlightColor": 16737945,
"selectColor": 3407641,
"quality": "auto",
"fontFamily": "sans-serif",
"fontQuality": 3,
"fontStyle": "normal",
"fontVariant": "normal",
"fontWeight": "normal",
"sizeFactor": 1,
"borderWidth": 0,
"borderColor": 8421504,
"offsetX": 0,
"offsetY": 0,
"offsetZ": 0,
"background": true,
"backgroundMargin": 0.2,
"backgroundColor": 16775930,
"backgroundOpacity": 0.9,
"attachment": "middle-center",
"levels": []
}
```
----------------------------

View File

@@ -1,86 +0,0 @@
What is VolumeServer
=====================
VolumeServer is a service for accessing subsets of volumetric density data. It automatically downsamples the data depending on the volume of the requested region to reduce the bandwidth requirements and provide near-instant access to even the largest data sets.
It uses the text based CIF and BinaryCIF formats to deliver the data to the client.
For quick info about the benefits of using the server, check out the [examples](examples.md).
Installing and Running
=====================
Requires nodejs 8+.
## From GitHub
```
git clone https://github.com/molstar/molstar
npm install
```
Afterwards, build the project source:
```
npm run build-tsc
```
and run the server by
```
node lib/commonjs/servers/volume/server
```
## From NPM
```
npm install --production molstar
./volume-server
```
(or ``node node_modules\.bin\volume-server`` in Windows).
The NPM package contains all the tools mentioned here as "binaries":
- ``volume-server``
- ``volume-server-pack``
- ``volume-server-query``
### Production use
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
### Memory issues
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
## Preparing the Data
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
## Local Mode
The program ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
## Navigating the Source Code
The source code is split into 2 mains parts: ``pack`` and ``server``:
- The ``pack`` part provides the means of converting CCP4 files into the internal block format.
- The ``server`` includes
- ``query``: the main part of the server that handles a query. ``execute.ts`` is the "entry point".
- ``algebra``: linear, "coordinate", and "box" algebra provides the means for calculations necessary to concent a user query into a menaningful response.
- API wrapper that handles the requests.
Consuming the Data
==================
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
The [Data Format](DataFormat.md) document gives a detailed description of the server response format.
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.

BIN
examples/mvs/1h9t.mvsx Normal file

Binary file not shown.

1499
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "4.0.0",
"version": "4.2.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -104,66 +104,68 @@
"Dominik Tichy <tichydominik451@gmail.com>",
"Yana Rose <yana.v.rose@gmail.com>",
"Yakov Pechersky <ffxen158@gmail.com>",
"Christian Dominguez <christian.99dominguez@gmail.com>"
"Christian Dominguez <christian.99dominguez@gmail.com>",
"Cai Huiyu <szmun.caihy@gmail.com>",
"Ryan DiRisio <rjdiris@gmail.com>"
],
"license": "MIT",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/gl": "^6.0.5",
"@types/pngjs": "^6.0.4",
"@types/pngjs": "^6.0.5",
"@types/jest": "^29.5.12",
"@types/react": "^18.2.52",
"@types/react-dom": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"benchmark": "^2.1.4",
"concurrently": "^8.2.2",
"cpx2": "^7.0.1",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.10.0",
"eslint": "^8.56.0",
"css-loader": "^7.1.1",
"eslint": "^8.57.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^11.2.0",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"jpeg-js": "^0.4.4",
"mini-css-extract-plugin": "^2.8.0",
"mini-css-extract-plugin": "^2.9.0",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.70.0",
"sass-loader": "^14.1.0",
"simple-git": "^3.22.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.76.0",
"sass-loader": "^14.2.1",
"simple-git": "^3.24.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.4",
"style-loader": "^4.0.0",
"ts-jest": "^29.1.2",
"typescript": "^5.3.3",
"webpack": "^5.90.1",
"typescript": "^5.4.5",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@types/argparse": "^2.0.14",
"@types/argparse": "^2.0.16",
"@types/benchmark": "^2.1.5",
"@types/compression": "1.7.5",
"@types/express": "^4.17.21",
"@types/node": "^18.19.14",
"@types/node": "^18.19.31",
"@types/node-fetch": "^2.6.11",
"@types/swagger-ui-dist": "3.30.4",
"argparse": "^2.0.1",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.18.2",
"express": "^4.19.2",
"h264-mp4-encoder": "^1.0.12",
"immer": "^10.0.3",
"immer": "^10.1.1",
"immutable": "^4.3.5",
"io-ts": "^2.2.21",
"node-fetch": "^2.7.0",
"react-markdown": "^9.0.1",
"rxjs": "^7.8.1",
"swagger-ui-dist": "^5.11.2",
"swagger-ui-dist": "^5.17.2",
"tslib": "^2.6.2",
"util.promisify": "^1.1.2",
"xhr2": "^0.2.1"

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
@@ -96,12 +96,12 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
const compRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
const funcRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:func:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `func:`, label: 'function', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.applyOrUpdateTagged('group:func:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `func:`, label: 'function', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
if (entities._rowCount > 1) {
@@ -159,7 +159,7 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
parent.cell!.state.isCollapsed = false;
const group = await state.build()
.to(parent)
.applyOrUpdateTagged(`group:comp:${n}`, MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.applyOrUpdateTagged(`group:comp:${n}`, MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
compGroups.set(n, group);
}
@@ -171,7 +171,7 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
const color = colorIdx !== undefined ? baseFuncColors[colorIdx] : ColorNames.white;
const group = await state.build()
.to(funcRoot)
.applyOrUpdateTagged(`group:func:${f}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
.applyOrUpdateTagged(`group:func:${f}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
funcGroups.set(f, group);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -124,7 +124,7 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
async function addGroup(g: GenericGroup, cell: StateObjectSelector, parent: string) {
const group = await state.build()
.to(cell)
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit();
if (g.children) {
@@ -137,7 +137,7 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
for (const r of manifest.roots) {
const root = await state.build()
.toRoot()
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
if (r.children) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -114,7 +114,7 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
const entRoot = await state.build()
.toRoot()
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit();
const getEntityType = (i: number) => {
@@ -148,7 +148,7 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
const color = colorIdx !== undefined ? baseEntColors[colorIdx] : ColorNames.white;
const group = await state.build()
.to(entRoot)
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
entGroups.set(t, group);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -97,12 +97,12 @@ export async function createPetworldHierarchy(plugin: PluginContext, trajectory:
const group = await state.build()
.toRoot()
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
.commit({ revertOnError: true });
await state.build()
.to(group)
.applyOrUpdateTagged(`group:ent:mem`, MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.applyOrUpdateTagged(`group:ent:mem`, MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
.commit();
const colors = getDistinctBaseColors(other.length, 0);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -111,6 +111,7 @@ export const ColorParams = {
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
};
export type ColorProps = PD.Values<typeof ColorParams>
@@ -123,6 +124,7 @@ export const RootParams = {
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
};
export const LightnessParams = {
@@ -134,6 +136,10 @@ export const OpacityParams = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
};
export const EmissiveParams = {
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
};
export const PatternParams = {
frequency: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
amplitude: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
@@ -248,6 +254,7 @@ export const MesoscaleGroupParams = {
color: PD.Group(RootParams),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
lod: PD.Group(LodParams),
clip: PD.Group(SimpleClipParams),
};
@@ -504,7 +511,7 @@ export function getEntityLabel(plugin: PluginContext, cell: StateObjectCell) {
export async function updateColors(plugin: PluginContext, values: PD.Values, tag: string, filter: string) {
const update = plugin.state.data.build();
const { type, value, shift, lightness, alpha } = values;
const { type, value, shift, lightness, alpha, emissive } = values;
if (type === 'group-generate' || type === 'group-uniform') {
const groups = getAllLeafGroups(plugin, tag);
@@ -528,11 +535,13 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
old.colorTheme.params.lightness = lightness;
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
} else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
@@ -542,6 +551,7 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
old.color.value = baseColors[i];
old.color.lightness = lightness;
old.color.alpha = alpha;
old.color.emissive = emissive;
});
}
} else if (type === 'generate' || type === 'uniform') {
@@ -560,11 +570,13 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
old.colorTheme.params.lightness = lightness;
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
} else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
@@ -576,6 +588,7 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
old.color.value = value;
old.color.lightness = lightness;
old.color.alpha = alpha;
old.color.emissive = emissive;
});
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -18,7 +18,7 @@ import { CombinedColorControl } from '../../../mol-plugin-ui/controls/color';
import { MarkerAction } from '../../../mol-util/marker-action';
import { EveryLoci, Loci } from '../../../mol-model/loci';
import { deepEqual } from '../../../mol-util';
import { ColorValueParam, ColorParams, ColorProps, DimLightness, LightnessParams, LodParams, MesoscaleGroup, MesoscaleGroupProps, OpacityParams, SimpleClipParams, SimpleClipProps, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, GraphicsMode, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups } from '../data/state';
import { ColorValueParam, ColorParams, ColorProps, DimLightness, LightnessParams, LodParams, MesoscaleGroup, MesoscaleGroupProps, OpacityParams, SimpleClipParams, SimpleClipProps, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, GraphicsMode, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups, EmissiveParams } from '../data/state';
import React from 'react';
import { MesoscaleExplorerState } from '../app';
import { StructureElement } from '../../../mol-model/structure/structure/element';
@@ -493,7 +493,7 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
updateColor = (values: ColorProps) => {
const update = this.plugin.state.data.build();
const { value, type, lightness, alpha } = values;
const { value, type, lightness, alpha, emissive } = values;
const entities = this.filteredEntities;
@@ -511,11 +511,13 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
old.colorTheme.params.lightness = lightness;
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
} else {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
@@ -792,6 +794,12 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
};
}
get emissiveValue(): { emissive: number } | undefined {
return {
emissive: this.cell.transform.params?.type?.params.emissive ?? this.cell.transform.params?.emissive ?? 0
};
}
get clipValue(): Clip.Props | undefined {
return this.cell.transform.params.type?.params.clip ?? this.cell.transform.params.clip;
}
@@ -860,6 +868,16 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
}).commit();
};
updateEmissive = (values: PD.Values) => {
return this.plugin.build().to(this.ref).update(old => {
if (old.type) {
old.type.params.emissive = values.emissive;
} else {
old.emissive = values.emissive;
}
}).commit();
};
updateClip = (props: Clip.Props) => {
const params = this.cell.transform.params;
const clip = params.type ? params.type.params.clip : params.clip;
@@ -907,6 +925,7 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
const colorValue = this.colorValue;
const lightnessValue = this.lightnessValue;
const opacityValue = this.opacityValue;
const emissiveValue = this.emissiveValue;
const lodValue = this.lodValue;
const patternValue = this.patternValue;
@@ -936,6 +955,7 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
<CombinedColorControl param={ColorValueParam} value={colorValue ?? Color(0xFFFFFF)} onChange={this.updateColor} name='color' hideNameRow />
<ParameterControls params={LightnessParams} values={lightnessValue} onChangeValues={this.updateLightness} />
<ParameterControls params={OpacityParams} values={opacityValue} onChangeValues={this.updateOpacity} />
<ParameterControls params={EmissiveParams} values={emissiveValue} onChangeValues={this.updateEmissive} />
{patternValue && <ParameterControls params={PatternParams} values={patternValue} onChangeValues={this.updatePattern} />}
</ControlGroup>
</div>}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 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>
@@ -15,6 +15,7 @@ import { QualityAssessment } from '../../extensions/model-archive/quality-assess
import { ModelExport } from '../../extensions/model-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { loadMVSX } from '../../extensions/mvs/components/formats';
import { loadMVS } from '../../extensions/mvs/load';
import { MVSData } from '../../extensions/mvs/mvs-data';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
@@ -50,6 +51,7 @@ 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 { Task } from '../../mol-task';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
@@ -468,25 +470,46 @@ export class Viewer {
return { model, coords, preset };
}
async loadMvsFromUrl(url: string, format: 'mvsj') {
async loadMvsFromUrl(url: string, format: 'mvsj' | 'mvsx', options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
if (format === 'mvsj') {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'string' }));
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: url });
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: url, ...options });
} else if (format === 'mvsx') {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
const parsed = await loadMVSX(this.plugin, ctx, data);
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
}));
} else {
throw new Error(`Unknown MolViewSpec format: ${format}`);
}
// We might add more formats in the future
}
async loadMvsData(data: string, format: 'mvsj') {
/** Load MolViewSpec from `data`.
* If `format` is 'mvsj', `data` must be a string or a Uint8Array containing a UTF8-encoded string.
* If `format` is 'mvsx', `data` must be a Uint8Array or a string containing base64-encoded binary data prefixed with 'base64,'. */
async loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx', options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
if (typeof data === 'string' && data.startsWith('base64')) {
data = Uint8Array.from(atob(data.substring(7)), c => c.charCodeAt(0)); // Decode base64 string to Uint8Array
}
if (format === 'mvsj') {
if (typeof data !== 'string') {
data = new TextDecoder().decode(data); // Decode Uint8Array to string using UTF8
}
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: undefined });
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: undefined, ...options });
} else if (format === 'mvsx') {
if (typeof data === 'string') {
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
}
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
const parsed = await loadMVSX(this.plugin, ctx, data as Uint8Array);
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
}));
} else {
throw new Error(`Unknown MolViewSpec format: ${format}`);
}
// We might add more formats in the future
}
handleResize() {

View File

@@ -41,6 +41,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'pdbx_related_db_id':
case 'sequence_dep':
case 'pdb_id':
case 'pdb_id_u': // should be case insensitve, but can't express that
case 'emd_id':
// todo, consider adding specialised fields
case 'yyyy-mm-dd':

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*
@@ -17,19 +18,21 @@ import path from 'path';
import pngjs from 'pngjs';
import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
import { setCanvasModule } from '../../mol-geo/geometry/text/font-atlas';
import { PluginContext } from '../../mol-plugin/context';
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
import { DefaultPluginSpec, PluginSpec } from '../../mol-plugin/spec';
import { ExternalModules, defaultCanvas3DParams } from '../../mol-plugin/util/headless-screenshot';
import { Task } from '../../mol-task';
import { setFSModule } from '../../mol-util/data-source';
import { onelinerJsonString } from '../../mol-util/json';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
// MolViewSpec must be imported after HeadlessPluginContext
import { MolViewSpec } from '../../extensions/mvs/behavior';
import { loadMVSX } from '../../extensions/mvs/components/formats';
import { loadMVS } from '../../extensions/mvs/load';
import { MVSData } from '../../extensions/mvs/mvs-data';
import { setCanvasModule } from '../../mol-geo/geometry/text/font-atlas';
setFSModule(fs);
@@ -48,7 +51,7 @@ interface Args {
/** Return parsed command line arguments for `main` */
function parseArguments(): Args {
const parser = new ArgumentParser({ description: 'Command-line application for rendering images from MolViewSpec files' });
parser.add_argument('-i', '--input', { required: true, nargs: '+', help: 'Input file(s) in .mvsj format' });
parser.add_argument('-i', '--input', { required: true, nargs: '+', help: 'Input file(s) in .mvsj or .mvsx format. File format is inferred from the file extension.' });
parser.add_argument('-o', '--output', { required: true, nargs: '+', help: 'File path(s) for output files (one output path for each input file). Output format is inferred from the file extension (.png or .jpg)' });
parser.add_argument('-s', '--size', { help: `Output image resolution, {width}x{height}. Default: ${DEFAULT_SIZE}.`, default: DEFAULT_SIZE });
parser.add_argument('-m', '--molj', { action: 'store_true', help: `Save Mol* state (.molj) in addition to rendered images (use the same output file paths but with .molj extension)` });
@@ -75,10 +78,22 @@ async function main(args: Args): Promise<void> {
const output = args.output[i];
console.log(`Processing ${input} -> ${output}`);
const data = fs.readFileSync(input, { encoding: 'utf8' });
const mvsData = MVSData.fromMVSJ(data);
let mvsData: MVSData;
let sourceUrl: string | undefined;
if (input.toLowerCase().endsWith('.mvsj')) {
const data = fs.readFileSync(input, { encoding: 'utf8' });
mvsData = MVSData.fromMVSJ(data);
sourceUrl = `file://${path.resolve(input)}`;
} else if (input.toLowerCase().endsWith('.mvsx')) {
const data = fs.readFileSync(input);
const mvsx = await plugin.runTask(Task.create('Load MVSX', async ctx => loadMVSX(plugin, ctx, data)));
mvsData = mvsx.mvsData;
sourceUrl = mvsx.sourceUrl;
} else {
throw new Error(`Input file name must end with .mvsj or .mvsx: ${input}`);
}
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: sourceUrl });
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: `file://${path.resolve(input)}` });
fs.mkdirSync(path.dirname(output), { recursive: true });
if (args.molj) {
await plugin.saveStateSnapshot(withExtension(output, '.molj'));

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -80,6 +80,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
saturation: 0,
opacity: 1,
blur: 0.3,
rotation: { x: 0, y: 0, z: 0 },
}
}
}, 'Purple Nebula Skybox'],

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -170,8 +170,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
}
private addMaterial(metalness: number, roughness: number, doubleSided: boolean, alpha: boolean) {
const hash = `${metalness}|${roughness}|${doubleSided}`;
private addMaterial(metalness: number, roughness: number, emissive: number, doubleSided: boolean, alpha: boolean) {
const hash = `${metalness}|${roughness}|${emissive}|${doubleSided}`;
if (!this.materialMap.has(hash)) {
this.materialMap.set(hash, this.materials.length);
this.materials.push({
@@ -180,6 +180,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
metallicFactor: metalness,
roughnessFactor: roughness
},
emissiveFactor: [emissive, emissive, emissive],
doubleSided,
alphaMode: alpha ? 'BLEND' : 'OPAQUE',
});
@@ -200,10 +201,11 @@ export class GlbExporter extends MeshExporter<GlbData> {
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
const emissive = values.uEmissive.ref.value;
const doubleSided = values.uDoubleSided?.ref.value || values.hasReflection.ref.value;
const alpha = values.uAlpha.ref.value < 1;
const material = this.addMaterial(metalness, roughness, doubleSided, alpha);
const material = this.addMaterial(metalness, roughness, emissive, doubleSided, alpha);
let interpolatedColors: Uint8Array | undefined;
if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {

View File

@@ -13,6 +13,7 @@ import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { PluginContext } from '../../mol-plugin/context';
import { StructureRepresentationProvider } from '../../mol-repr/structure/representation';
import { StateAction } from '../../mol-state';
import { Task } from '../../mol-task';
import { ColorTheme } from '../../mol-theme/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { MVSAnnotationColorThemeProvider } from './components/annotation-color-theme';
@@ -21,7 +22,7 @@ import { MVSAnnotationsProvider } from './components/annotation-prop';
import { MVSAnnotationTooltipsLabelProvider, MVSAnnotationTooltipsProvider } from './components/annotation-tooltips-prop';
import { CustomLabelRepresentationProvider } from './components/custom-label/representation';
import { CustomTooltipsLabelProvider, CustomTooltipsProvider } from './components/custom-tooltips-prop';
import { LoadMvsData, MVSJFormatProvider } from './components/formats';
import { LoadMvsData, MVSJFormatProvider, MVSXFormatProvider, loadMVSX } from './components/formats';
import { IsMVSModelProvider } from './components/is-mvs-model-prop';
import { makeMultilayerColorThemeProvider } from './components/multilayer-color-theme';
import { loadMVS } from './load';
@@ -76,6 +77,7 @@ export const MolViewSpec = PluginBehavior.create<{ autoAttach: boolean }>({
],
dataFormats: [
{ name: 'MVSJ', provider: MVSJFormatProvider },
{ name: 'MVSX', provider: MVSXFormatProvider },
],
actions: [
LoadMvsData,
@@ -158,18 +160,32 @@ interface DragAndDropHandler {
handle: PluginDragAndDropHandler,
}
/** DragAndDropHandler handler for `.mvsj` files */
/** DragAndDropHandler handler for `.mvsj` and `.mvsx` files */
const MVSDragAndDropHandler: DragAndDropHandler = {
name: 'mvs-mvsj',
/** Load .mvsj files. Delete previous plugin state before loading.
* If multiple files are provided, merge their MVS data into one state. */
name: 'mvs-mvsj-mvsx',
/** Load .mvsj and .mvsx files. Delete previous plugin state before loading.
* If multiple files are provided, merge their MVS data into one state.
* Return `true` if at least one file has been loaded. */
async handle(files: File[], plugin: PluginContext): Promise<boolean> {
let applied = false;
for (const file of files) {
if (file.name.toLowerCase().endsWith('.mvsj')) {
const data = await file.text();
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: !applied, sourceUrl: undefined });
const task = Task.create('Load MVSJ file', async ctx => {
const data = await file.text();
const mvsData = MVSData.fromMVSJ(data);
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: !applied, sourceUrl: undefined });
});
await plugin.runTask(task);
applied = true;
}
if (file.name.toLowerCase().endsWith('.mvsx')) {
const task = Task.create('Load MVSX file', async ctx => {
const buffer = await file.arrayBuffer();
const array = new Uint8Array(buffer);
const parsed = await loadMVSX(plugin, ctx, array);
await loadMVS(plugin, parsed.mvsData, { sanityChecks: true, replaceExisting: !applied, sourceUrl: parsed.sourceUrl });
});
await plugin.runTask(task);
applied = true;
}
}

View File

@@ -5,6 +5,7 @@
*/
import { Camera } from '../../mol-canvas3d/camera';
import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Sphere3D } from '../../mol-math/geometry';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
@@ -33,6 +34,13 @@ const DefaultCanvasBackgroundColor = ColorNames.white;
const _tmpVec = Vec3();
/** Set the camera position to the current position (thus suppress automatic adjustment). */
export async function suppressCameraAutoreset(plugin: PluginContext) {
const snapshot: Partial<Camera.Snapshot> = { ...plugin.canvas3d?.camera.state, radius: Infinity }; // `radius: Infinity` avoids clipping when the scene expands
adjustSceneRadiusFactor(plugin, snapshot.target);
await PluginCommands.Camera.SetSnapshot(plugin, { snapshot });
}
/** Set the camera based on a camera node params. */
export async function setCamera(plugin: PluginContext, params: ParamsOfKind<MolstarTree, 'camera'>) {
const target = Vec3.create(...params.target);
@@ -40,7 +48,8 @@ export async function setCamera(plugin: PluginContext, params: ParamsOfKind<Mols
if (plugin.canvas3d) position = fovAdjustedPosition(target, position, plugin.canvas3d.camera.state.mode, plugin.canvas3d.camera.state.fov);
const up = Vec3.create(...params.up);
Vec3.orthogonalize(up, Vec3.sub(_tmpVec, target, position), up);
const snapshot: Partial<Camera.Snapshot> = { target, position, up, radius: 10_000, 'radiusMax': 10_000 };
const snapshot: Partial<Camera.Snapshot> = { target, position, up, radius: Infinity }; // `radius: Infinity` avoids clipping (ensures covering the whole scene)
adjustSceneRadiusFactor(plugin, snapshot.target);
await PluginCommands.Camera.SetSnapshot(plugin, { snapshot });
}
@@ -69,10 +78,26 @@ export async function setFocus(plugin: PluginContext, structureNodeSelector: Sta
up,
direction,
});
resetSceneRadiusFactor(plugin);
await PluginCommands.Camera.SetSnapshot(plugin, { snapshot });
}
}
/** Adjust `sceneRadiusFactor` property so that the current scene is not cropped */
function adjustSceneRadiusFactor(plugin: PluginContext, cameraTarget: Vec3 | undefined) {
if (!cameraTarget) return;
const boundingSphere = getPluginBoundingSphere(plugin);
const offset = Vec3.distance(cameraTarget, boundingSphere.center);
const sceneRadiusFactor = boundingSphere.radius > 0 ? ((boundingSphere.radius + offset) / boundingSphere.radius) : 1;
plugin.canvas3d?.setProps({ sceneRadiusFactor });
}
/** Reset `sceneRadiusFactor` property to the default value */
function resetSceneRadiusFactor(plugin: PluginContext) {
const sceneRadiusFactor = Canvas3DParams.sceneRadiusFactor.defaultValue;
plugin.canvas3d?.setProps({ sceneRadiusFactor });
}
/** Return camera snapshot for focusing a sphere with given `center` and `radius`,
* while ensuring given view `direction` (aligns with vector position->target)
* and `up` (aligns with screen Y axis). */

View File

@@ -1,17 +1,19 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { hashFnv32a } from '../../../mol-data/util';
import { DataFormatProvider } from '../../../mol-plugin-state/formats/provider';
import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
import { Download } from '../../../mol-plugin-state/transforms/data';
import { PluginContext } from '../../../mol-plugin/context';
import { StateAction, StateObjectRef } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { Asset } from '../../../mol-util/assets';
import { RuntimeContext, Task } from '../../../mol-task';
import { Asset, AssetManager } from '../../../mol-util/assets';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { unzip } from '../../../mol-util/zip/zip';
import { loadMVS } from '../load';
import { MVSData } from '../mvs-data';
import { MVSTransform } from './annotation-structure-component';
@@ -23,7 +25,7 @@ export class Mvs extends SO.Create<{ mvsData: MVSData, sourceUrl?: string }>({ n
/** Transformer for parsing data in MVSJ format */
export const ParseMVSJ = MVSTransform({
name: 'mvs-parse-mvsj',
display: { name: 'MVS Annotation Component', description: 'A molecular structure component defined by MVS annotation data.' },
display: { name: 'MolViewSpec from MVSJ', description: 'Create MolViewSpec view from MVSJ data' },
from: SO.Data.String,
to: Mvs,
})({
@@ -34,17 +36,29 @@ export const ParseMVSJ = MVSTransform({
},
});
/** If the PluginStateObject `pso` comes from a Download transform, try to get its `url` parameter. */
function tryGetDownloadUrl(pso: SO.Data.String, plugin: PluginContext): string | undefined {
const theCell = plugin.state.data.selectQ(q => q.ofTransformer(Download)).find(cell => cell.obj === pso);
const urlParam = theCell?.transform.params?.url;
return urlParam ? Asset.getUrl(urlParam) : undefined;
}
/** Transformer for parsing data in MVSX format (= zipped MVSJ + referenced files like structures and annotations) */
export const ParseMVSX = MVSTransform({
name: 'mvs-parse-mvsx',
display: { name: 'MolViewSpec from MVSX', description: 'Create MolViewSpec view from MVSX data' },
from: SO.Data.Binary,
to: Mvs,
params: {
mainFilePath: PD.Text('index.mvsj'),
},
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Parse MVSX file', async ctx => {
const data = await loadMVSX(plugin, ctx, a.data, params.mainFilePath);
return new Mvs(data);
});
},
});
/** Params for the `LoadMvsData` action */
const LoadMvsDataParams = {
export const LoadMvsDataParams = {
replaceExisting: PD.Boolean(false, { description: 'If true, the loaded MVS view will replace the current state; if false, the MVS view will be added to the current state.' }),
keepCamera: PD.Boolean(false, { description: 'If true, any camera positioning from the MVS state will be ignored and the current camera position will be kept.' }),
};
/** State action which loads a MVS view into Mol* */
@@ -54,7 +68,7 @@ export const LoadMvsData = StateAction.build({
params: LoadMvsDataParams,
})(({ a, params }, plugin: PluginContext) => Task.create('Load MVS Data', async () => {
const { mvsData, sourceUrl } = a.data;
await loadMVS(plugin, mvsData, { replaceExisting: params.replaceExisting, sourceUrl: sourceUrl });
await loadMVS(plugin, mvsData, { replaceExisting: params.replaceExisting, keepCamera: params.keepCamera, sourceUrl: sourceUrl });
}));
@@ -75,3 +89,73 @@ export const MVSJFormatProvider: DataFormatProvider<{}, StateObjectRef<Mvs>, any
return await plugin.state.data.applyAction(LoadMvsData, params, ref).run();
},
});
/** Data format provider for MVSX format.
* If Visuals:On, it will load the parsed MVS view;
* otherwise it will just create a plugin state object with parsed data. */
export const MVSXFormatProvider: DataFormatProvider<{}, StateObjectRef<Mvs>, any> = DataFormatProvider({
label: 'MVSX',
description: 'MVSX',
category: 'Miscellaneous',
binaryExtensions: ['mvsx'],
parse: async (plugin, data) => {
return plugin.state.data.build().to(data).apply(ParseMVSX).commit();
},
visuals: MVSJFormatProvider.visuals,
});
/** Parse binary data `data` as MVSX archive,
* add all contained files to `plugin`'s asset manager,
* and parse the main file in the archive as MVSJ.
* Return parsed MVS data and `sourceUrl` for resolution of relative URIs. */
export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext, data: Uint8Array, mainFilePath: string = 'index.mvsj'): Promise<{ mvsData: MVSData, sourceUrl: string }> {
const archiveId = `ni,fnv1a;${hashFnv32a(data)}`;
let files: { [path: string]: Uint8Array };
try {
files = await unzip(runtimeCtx, data) as typeof files;
} catch (err) {
plugin.log.error('Invalid MVSX file');
throw err;
}
for (const path in files) {
const url = arcpUri(archiveId, path);
ensureUrlAsset(plugin.managers.asset, url, files[path]);
}
const mainFile = files[mainFilePath];
if (!mainFile) throw new Error(`File ${mainFilePath} not found in the MVSX archive`);
const mvsData = MVSData.fromMVSJ(decodeUtf8(mainFile));
const sourceUrl = arcpUri(archiveId, mainFilePath);
return { mvsData, sourceUrl };
}
/** If the PluginStateObject `pso` comes from a Download transform, try to get its `url` parameter. */
function tryGetDownloadUrl(pso: SO.Data.String, plugin: PluginContext): string | undefined {
const theCell = plugin.state.data.selectQ(q => q.ofTransformer(Download)).find(cell => cell.obj === pso);
const urlParam = theCell?.transform.params?.url;
return urlParam ? Asset.getUrl(urlParam) : undefined;
}
/** Return a URI referencing a file within an archive, using ARCP scheme (https://arxiv.org/pdf/1809.06935.pdf).
* `archiveId` corresponds to the `authority` part of URI (e.g. 'uuid,EYVwjDiZhM20PWbF1OWWvQ' or 'ni,fnv1a;938511930')
* `path` corresponds to the path to a file within the archive */
function arcpUri(archiveId: string, path: string): string {
return new URL(path, `arcp://${archiveId}/`).href;
}
/** Add a URL asset to asset manager.
* Skip if an asset with the same URL already exists. */
function ensureUrlAsset(manager: AssetManager, url: string, data: Uint8Array) {
const asset = Asset.getUrlAsset(manager, url);
if (!manager.has(asset)) {
const filename = url.split('/').pop() ?? 'file';
manager.set(asset, new File([data], filename));
}
}
/** Decode bytes to text using UTF-8 encoding */
function decodeUtf8(bytes: Uint8Array): string {
_decoder ??= new TextDecoder();
return _decoder.decode(bytes);
}
let _decoder: TextDecoder | undefined;

View File

@@ -10,7 +10,7 @@ import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/rep
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectSelector } from '../../mol-state';
import { MolViewSpec } from './behavior';
import { setCamera, setCanvas, setFocus } from './camera';
import { setCamera, setCanvas, setFocus, suppressCameraAutoreset } from './camera';
import { MVSAnnotationsProvider } from './components/annotation-prop';
import { MVSAnnotationStructureComponent } from './components/annotation-structure-component';
import { MVSAnnotationTooltipsProvider } from './components/annotation-tooltips-prop';
@@ -27,9 +27,10 @@ import { MVSTreeSchema } from './tree/mvs/mvs-tree';
/** Load a MolViewSpec (MVS) tree into the Mol* plugin.
* If `options.replaceExisting`, remove all objects in the current Mol* state; otherwise add to the current state.
* If `options.keepCamera`, ignore any camera positioning from the MVS state and keep the current camera position instead.
* If `options.sanityChecks`, run some sanity checks and print potential issues to the console.
* `options.sourceUrl` serves as the base for resolving relative URLs/URIs and may itself be relative to the window URL. */
export async function loadMVS(plugin: PluginContext, data: MVSData, options: { replaceExisting?: boolean, sanityChecks?: boolean, sourceUrl?: string } = {}) {
export async function loadMVS(plugin: PluginContext, data: MVSData, options: { replaceExisting?: boolean, keepCamera?: boolean, sanityChecks?: boolean, sourceUrl?: string } = {}) {
try {
// console.log(`MVS tree:\n${MVSData.toPrettyString(data)}`)
validateTree(MVSTreeSchema, data.root, 'MVS');
@@ -47,7 +48,7 @@ export async function loadMVS(plugin: PluginContext, data: MVSData, options: { r
/** Load a `MolstarTree` into the Mol* plugin.
* If `replaceExisting`, remove all objects in the current Mol* state; otherwise add to the current state. */
async function loadMolstarTree(plugin: PluginContext, tree: MolstarTree, options?: { replaceExisting?: boolean }) {
async function loadMolstarTree(plugin: PluginContext, tree: MolstarTree, options?: { replaceExisting?: boolean, keepCamera?: boolean }) {
const mvsExtensionLoaded = plugin.state.hasBehavior(MolViewSpec);
if (!mvsExtensionLoaded) throw new Error('MolViewSpec extension is not loaded.');
@@ -56,12 +57,17 @@ async function loadMolstarTree(plugin: PluginContext, tree: MolstarTree, options
await loadTree(plugin, tree, MolstarLoadingActions, context, options);
setCanvas(plugin, context.canvas);
if (context.focus?.kind === 'camera') {
await setCamera(plugin, context.focus.params);
} else if (context.focus?.kind === 'focus') {
await setFocus(plugin, context.focus.focusTarget, context.focus.params);
if (options?.keepCamera) {
await suppressCameraAutoreset(plugin);
} else {
await setFocus(plugin, undefined, undefined);
if (context.focus?.kind === 'camera') {
await setCamera(plugin, context.focus.params);
} else if (context.focus?.kind === 'focus') {
await setFocus(plugin, context.focus.focusTarget, context.focus.params);
} else {
await setFocus(plugin, undefined, undefined);
}
}
}

View File

@@ -363,7 +363,7 @@ namespace Canvas3D {
let currentTime = 0;
updateViewport();
const scene = Scene.create(webgl, ctx.props.transparency);
const scene = Scene.create(webgl, passes.draw.transparency);
function getSceneRadius() {
return scene.boundingSphere.radius * p.sceneRadiusFactor;
@@ -804,7 +804,7 @@ namespace Canvas3D {
addConsoleStatsProvider(consoleStats);
const ctxChangedSub = ctx.changed?.subscribe(() => {
scene.setTransparency(ctx.props.transparency);
scene.setTransparency(passes.draw.transparency);
requestDraw();
});

View File

@@ -193,7 +193,7 @@ namespace TrackballControls {
}
function getRotateFactor() {
const aspectRatio = input.width / input.height;
const aspectRatio = (input.width / input.height) || 1;
return p.rotateSpeed * input.pixelRatio * aspectRatio;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -23,7 +23,9 @@ import { Vec2 } from '../../mol-math/linear-algebra/3d/vec2';
import { Color } from '../../mol-util/color';
import { Asset, AssetManager } from '../../mol-util/assets';
import { Vec4 } from '../../mol-math/linear-algebra/3d/vec4';
import { isPowerOfTwo } from '../../mol-math/misc';
import { degToRad, isPowerOfTwo } from '../../mol-math/misc';
import { Mat3 } from '../../mol-math/linear-algebra/3d/mat3';
import { Euler } from '../../mol-math/linear-algebra/3d/euler';
const SharedParams = {
opacity: PD.Numeric(1, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -51,6 +53,11 @@ const SkyboxParams = {
}, { isExpanded: true, label: 'Files' }),
}),
blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or when "EXT_shader_texture_lod" is available.' }),
rotation: PD.Group({
x: PD.Numeric(0, { min: 0, max: 360, step: 1 }, { immediateUpdate: true }),
y: PD.Numeric(0, { min: 0, max: 360, step: 1 }, { immediateUpdate: true }),
z: PD.Numeric(0, { min: 0, max: 360, step: 1 }, { immediateUpdate: true }),
}),
...SharedParams,
};
type SkyboxProps = PD.Values<typeof SkyboxParams>
@@ -173,6 +180,14 @@ export class BackgroundPass {
Mat4.invert(m, m);
ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m);
const r = this.renderable.values.uRotation.ref.value;
Mat3.fromEuler(r, Euler.create(
degToRad(props.rotation.x),
degToRad(props.rotation.y),
degToRad(props.rotation.z)
), 'XYZ');
ValueCell.update(this.renderable.values.uRotation, r);
ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur);
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
@@ -449,6 +464,7 @@ const BackgroundSchema = {
uOpacity: UniformSpec('f'),
uSaturation: UniformSpec('f'),
uLightness: UniformSpec('f'),
uRotation: UniformSpec('m3'),
dVariant: DefineSpec('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']),
};
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag, {
@@ -476,6 +492,7 @@ function getBackgroundRenderable(ctx: WebGLContext, width: number, height: numbe
uOpacity: ValueCell.create(1),
uSaturation: ValueCell.create(0),
uLightness: ValueCell.create(0),
uRotation: ValueCell.create(Mat3.identity()),
dVariant: ValueCell.create('skybox'),
};

View File

@@ -0,0 +1,349 @@
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Partially adapted from three.js, The MIT License, Copyright © 2010-2024 three.js authors
*/
import { CopyRenderable, QuadSchema, QuadValues, createCopyRenderable } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { TextureSpec, UniformSpec, DefineSpec, Values } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture, createNullTexture } from '../../mol-gl/webgl/texture';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { Viewport } from '../camera/util';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { isTimingMode } from '../../mol-util/debug';
import { composite_frag } from '../../mol-gl/shader/bloom/composite.frag';
import { luminosity_frag } from '../../mol-gl/shader/bloom/luminosity.frag';
import { blur_frag } from '../../mol-gl/shader/bloom/blur.frag';
import { memoize1 } from '../../mol-util/memoize';
import { PostprocessingProps } from './postprocessing';
const MipCount = 5;
export const BloomParams = {
strength: PD.Numeric(1, { min: 0, max: 3, step: 0.1 }),
radius: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
threshold: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { description: 'Luminosity threshold', hideIf: p => p.mode === 'emissive' }),
mode: PD.Select('emissive', [['luminosity', 'Luminosity'], ['emissive', 'Emissive']] as const),
};
export type BloomProps = PD.Values<typeof BloomParams>
export class BloomPass {
static isEnabled(props: PostprocessingProps) {
return props.bloom.name === 'on';
}
readonly emissiveTarget: RenderTarget;
private readonly luminosityTarget: RenderTarget;
private readonly horizontalBlurTargets: RenderTarget[] = [];
private readonly verticalBlurTargets: RenderTarget[] = [];
private readonly compositeTarget: RenderTarget;
private readonly luminosityRenderable: LuminosityRenderable;
private readonly blurRenderable: BlurRenderable;
private readonly compositeRenderable: CompositeRenderable;
private readonly copyRenderable: CopyRenderable;
constructor(private webgl: WebGLContext, width: number, height: number) {
this.emissiveTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear', 'rgba');
this.luminosityTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.compositeTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
let blurWidth = Math.round(width / 2);
let blurHeight = Math.round(height / 2);
for (let i = 0; i < MipCount; ++i) {
this.horizontalBlurTargets[i] = webgl.createRenderTarget(blurWidth, blurHeight, false, 'uint8', 'linear');
this.verticalBlurTargets[i] = webgl.createRenderTarget(blurWidth, blurHeight, false, 'uint8', 'linear');
blurWidth = Math.round(blurWidth / 2);
blurHeight = Math.round(blurHeight / 2);
}
const nullTexture = createNullTexture();
this.luminosityRenderable = getLuminosityRenderable(webgl, nullTexture, nullTexture, nullTexture);
this.blurRenderable = getBlurRenderable(webgl, nullTexture);
this.compositeRenderable = getCompositeRenderable(webgl, width, height, this.verticalBlurTargets[0].texture, this.verticalBlurTargets[1].texture, this.verticalBlurTargets[2].texture, this.verticalBlurTargets[3].texture, this.verticalBlurTargets[4].texture);
this.copyRenderable = createCopyRenderable(webgl, this.compositeTarget.texture);
}
setSize(width: number, height: number) {
const w = this.luminosityTarget.getWidth();
const h = this.luminosityTarget.getHeight();
if (width !== w || height !== h) {
this.emissiveTarget.setSize(width, height);
this.luminosityTarget.setSize(width, height);
this.compositeTarget.setSize(width, height);
let blurWidth = Math.round(width / 2);
let blurHeight = Math.round(height / 2);
for (let i = 0; i < MipCount; ++i) {
this.horizontalBlurTargets[i].setSize(blurWidth, blurHeight);
this.verticalBlurTargets[i].setSize(blurWidth, blurHeight);
blurWidth = Math.round(blurWidth / 2);
blurHeight = Math.round(blurHeight / 2);
}
ValueCell.update(this.luminosityRenderable.values.uTexSizeInv, Vec2.set(this.compositeRenderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
ValueCell.update(this.compositeRenderable.values.uTexSizeInv, Vec2.set(this.compositeRenderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
ValueCell.update(this.copyRenderable.values.uTexSize, Vec2.set(this.copyRenderable.values.uTexSize.ref.value, width, height));
}
}
update(input: Texture, emissive: Texture, depth: Texture, props: BloomProps) {
let luminosityNeedsUpdate = false;
if (this.luminosityRenderable.values.tColor.ref.value !== input) {
ValueCell.update(this.luminosityRenderable.values.tColor, input);
luminosityNeedsUpdate = true;
}
if (this.luminosityRenderable.values.tEmissive.ref.value !== emissive) {
ValueCell.update(this.luminosityRenderable.values.tEmissive, emissive);
luminosityNeedsUpdate = true;
}
if (this.luminosityRenderable.values.tDepth.ref.value !== depth) {
ValueCell.update(this.luminosityRenderable.values.tDepth, depth);
luminosityNeedsUpdate = true;
}
if (this.luminosityRenderable.values.dMode.ref.value !== props.mode) {
ValueCell.update(this.luminosityRenderable.values.dMode, props.mode);
luminosityNeedsUpdate = true;
}
ValueCell.updateIfChanged(this.luminosityRenderable.values.uLuminosityThreshold, props.threshold);
if (luminosityNeedsUpdate) {
this.luminosityRenderable.update();
}
//
let blurNeedsUpdate = false;
if (this.blurRenderable.values.tInput.ref.value !== input) {
ValueCell.update(this.blurRenderable.values.tInput, input);
blurNeedsUpdate = true;
}
if (blurNeedsUpdate) {
this.blurRenderable.update();
}
//
ValueCell.update(this.compositeRenderable.values.uBloomRadius, props.radius);
ValueCell.update(this.compositeRenderable.values.uBloomStrength, props.strength);
}
render(viewport: Viewport, target: RenderTarget | undefined) {
if (isTimingMode) this.webgl.timer.mark('BloomPass.render');
const { gl, state } = this.webgl;
const { x, y, width, height } = viewport;
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
// printTextureImage(readTexture(this.webgl, this.luminosityRenderable.values.tEmissive.ref.value, new Uint8Array(width * height * 4)), { scale: 0.25, id: 'emissive' });
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
// Extract Bright Areas
this.luminosityTarget.bind();
this.luminosityRenderable.render();
// printTextureImage(readTexture(this.webgl, this.luminosityTarget.texture, new Uint8Array(width * height * 4)), { scale: 0.25, id: 'luminosity' });
// Blur All the mips progressively
for (let i = 0; i < MipCount; ++i) {
const blurWidth = this.horizontalBlurTargets[i].getWidth();
const blurHeight = this.horizontalBlurTargets[i].getHeight();
state.viewport(0, 0, blurWidth, blurHeight);
state.scissor(0, 0, blurWidth, blurHeight);
ValueCell.update(this.blurRenderable.values.dKernelRadius, BlurKernelSizes[i]);
ValueCell.update(this.blurRenderable.values.uGaussianCoefficients, getBlurCoefficients(BlurKernelSizes[i]));
ValueCell.update(this.blurRenderable.values.uTexSizeInv, Vec2.set(this.blurRenderable.values.uTexSizeInv.ref.value, 1 / blurWidth, 1 / blurHeight));
this.horizontalBlurTargets[i].bind();
ValueCell.update(this.blurRenderable.values.tInput, i === 0 ? this.luminosityTarget.texture : this.verticalBlurTargets[i - 1].texture);
ValueCell.update(this.blurRenderable.values.uDirection, BlurDirectionX);
this.blurRenderable.update();
this.blurRenderable.render();
this.verticalBlurTargets[i].bind();
ValueCell.update(this.blurRenderable.values.tInput, this.horizontalBlurTargets[i].texture);
ValueCell.update(this.blurRenderable.values.uDirection, BlurDirectionY);
this.blurRenderable.update();
this.blurRenderable.render();
// printTextureImage(readTexture(this.webgl, this.verticalBlurTargets[i].texture, new Uint8Array(blurWidth * blurHeight * 4)), { scale: 0.25, id: `blur-${i}` });
}
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
// Composite All the mips
this.compositeTarget.bind();
this.compositeRenderable.update();
this.compositeRenderable.render();
// printTextureImage(readTexture(this.webgl, this.compositeTarget.texture, new Uint8Array(width * height * 4)), { scale: 0.25, id: 'composite' });
if (target) {
target.bind();
} else {
this.webgl.unbindFramebuffer();
}
state.enable(gl.BLEND);
state.blendFunc(gl.ONE, gl.ONE);
this.copyRenderable.render();
if (isTimingMode) this.webgl.timer.markEnd('BloomPass.render');
}
}
//
const LuminositySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tEmissive: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSizeInv: UniformSpec('v2'),
uDefaultColor: UniformSpec('v3'),
uDefaultOpacity: UniformSpec('f'),
uLuminosityThreshold: UniformSpec('f'),
uSmoothWidth: UniformSpec('f'),
dMode: DefineSpec('string', ['luminosity', 'emissive']),
};
const LuminosityShaderCode = ShaderCode('Bloom Luminosity', quad_vert, luminosity_frag);
type LuminosityRenderable = ComputeRenderable<Values<typeof LuminositySchema>>
function getLuminosityRenderable(ctx: WebGLContext, colorTexture: Texture, emissiveTexture: Texture, depthTexture: Texture): LuminosityRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof LuminositySchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
tEmissive: ValueCell.create(emissiveTexture),
tDepth: ValueCell.create(depthTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
uDefaultColor: ValueCell.create(Vec3()),
uDefaultOpacity: ValueCell.create(0),
uLuminosityThreshold: ValueCell.create(0),
uSmoothWidth: ValueCell.create(1),
dMode: ValueCell.create('emissive'),
};
const schema = { ...LuminositySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', LuminosityShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
//
function _getBlurCoefficients(kernelRadius: number) {
const coefficients: number[] = [];
for (let i = 0; i < kernelRadius; ++i) {
coefficients.push(0.39894 * Math.exp(-0.5 * i * i / (kernelRadius * kernelRadius)) / kernelRadius);
}
return coefficients;
}
const getBlurCoefficients = memoize1(_getBlurCoefficients);
const BlurKernelSizes = [3, 5, 7, 9, 11];
const BlurDirectionX = Vec2.create(1, 0);
const BlurDirectionY = Vec2.create(0, 1);
const BlurSchema = {
...QuadSchema,
tInput: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
uDirection: UniformSpec('v2'),
uGaussianCoefficients: UniformSpec('f[]'),
dKernelRadius: DefineSpec('number'),
};
const BlurShaderCode = ShaderCode('Bloom Blur', quad_vert, blur_frag);
type BlurRenderable = ComputeRenderable<Values<typeof BlurSchema>>
function getBlurRenderable(ctx: WebGLContext, inputTexture: Texture): BlurRenderable {
const width = inputTexture.getWidth();
const height = inputTexture.getHeight();
const values: Values<typeof BlurSchema> = {
...QuadValues,
tInput: ValueCell.create(inputTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
uDirection: ValueCell.create(Vec2()),
uGaussianCoefficients: ValueCell.create([]),
dKernelRadius: ValueCell.create(BlurKernelSizes[0]),
};
const schema = { ...BlurSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', BlurShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
//
const CompositeSchema = {
...QuadSchema,
tBlur1: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tBlur2: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tBlur3: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tBlur4: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tBlur5: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
uBloomStrength: UniformSpec('f'),
uBloomRadius: UniformSpec('f'),
uBloomFactors: UniformSpec('f[]'),
uBloomTints: UniformSpec('v3[]'),
};
const CompositeShaderCode = ShaderCode('Bloom Composite', quad_vert, composite_frag);
type CompositeRenderable = ComputeRenderable<Values<typeof CompositeSchema>>
function getCompositeRenderable(ctx: WebGLContext, width: number, height: number, blurTexture1: Texture, blurTexture2: Texture, blurTexture3: Texture, blurTexture4: Texture, blurTexture5: Texture): CompositeRenderable {
const values: Values<typeof CompositeSchema> = {
...QuadValues,
uTexSizeInv: ValueCell.create(Vec2.create(width, height)),
tBlur1: ValueCell.create(blurTexture1),
tBlur2: ValueCell.create(blurTexture2),
tBlur3: ValueCell.create(blurTexture3),
tBlur4: ValueCell.create(blurTexture4),
tBlur5: ValueCell.create(blurTexture5),
uBloomStrength: ValueCell.create(1),
uBloomRadius: ValueCell.create(0),
uBloomFactors: ValueCell.create([1.0, 0.8, 0.6, 0.4, 0.2]),
uBloomTints: ValueCell.create(new Array(5 * 3).fill(1)),
};
const schema = { ...CompositeSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CompositeShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

View File

@@ -221,12 +221,13 @@ export class DpoitPass {
}
static isSupported(webgl: WebGLContext) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, blendMinMax } } = webgl;
if (!textureFloat || !colorBufferFloat || !drawBuffers || !blendMinMax) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, depthTexture, blendMinMax } } = webgl;
if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers || !blendMinMax) {
if (isDebugMode) {
const missing: string[] = [];
if (!textureFloat) missing.push('textureFloat');
if (!colorBufferFloat) missing.push('colorBufferFloat');
if (!depthTexture) missing.push('depthTexture');
if (!drawBuffers) missing.push('drawBuffers');
if (!blendMinMax) missing.push('blendMinMax');
console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 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>
@@ -22,8 +22,9 @@ import { DpoitPass } from './dpoit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
import { MarkingPass, MarkingProps } from './marking';
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
import { isTimingMode } from '../../mol-util/debug';
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
import { AssetManager } from '../../mol-util/assets';
import { BloomPass } from './bloom';
type Props = {
postprocessing: PostprocessingProps;
@@ -48,23 +49,33 @@ export class DrawPass {
readonly depthTextureTransparent: Texture;
readonly depthTextureOpaque: Texture;
readonly packedDepth: boolean;
private depthTargetTransparent: RenderTarget;
private depthTargetOpaque: RenderTarget | null;
private copyFboTarget: CopyRenderable;
private copyFboPostprocessing: CopyRenderable;
private readonly wboit: WboitPass;
private readonly dpoit: DpoitPass;
private readonly marking: MarkingPass;
readonly wboit: WboitPass;
readonly dpoit: DpoitPass;
readonly marking: MarkingPass;
readonly postprocessing: PostprocessingPass;
private readonly antialiasing: AntialiasingPass;
readonly antialiasing: AntialiasingPass;
readonly bloom: BloomPass;
private transparencyMode: TransparencyMode = 'blended';
setTransparency(transparency: 'wboit' | 'dpoit' | 'blended') {
if (transparency === 'wboit') {
this.transparencyMode = this.wboit.supported ? 'wboit' : 'blended';
if (isDebugMode && !this.wboit.supported) {
console.log('Missing "wboit" support, falling back to "blended".');
}
} else if (transparency === 'dpoit') {
this.transparencyMode = this.dpoit.supported ? 'dpoit' : 'blended';
if (isDebugMode && !this.dpoit.supported) {
console.log('Missing "dpoit" support, falling back to "blended".');
}
} else {
this.transparencyMode = 'blended';
}
@@ -75,18 +86,27 @@ export class DrawPass {
}
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, transparency: 'wboit' | 'dpoit' | 'blended') {
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.depthTextureOpaque = this.colorTarget.depthTexture!;
this.packedDepth = !extensions.depthTexture;
this.depthTargetTransparent = webgl.createRenderTarget(width, height);
this.depthTextureTransparent = this.depthTargetTransparent.texture;
this.depthTargetOpaque = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTextureOpaque = this.depthTargetOpaque ? this.depthTargetOpaque.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
if (!this.packedDepth) {
this.depthTextureOpaque.define(width, height);
}
this.wboit = new WboitPass(webgl, width, height);
this.dpoit = new DpoitPass(webgl, width, height);
this.marking = new MarkingPass(webgl, width, height);
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.antialiasing = new AntialiasingPass(webgl, width, height);
this.bloom = new BloomPass(webgl, width, height);
this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = createCopyRenderable(webgl, this.postprocessing.target.texture);
@@ -107,6 +127,12 @@ export class DrawPass {
this.colorTarget.setSize(width, height);
this.depthTargetTransparent.setSize(width, height);
if (this.depthTargetOpaque) {
this.depthTargetOpaque.setSize(width, height);
} else {
this.depthTextureOpaque.define(width, height);
}
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
}
@@ -122,6 +148,7 @@ export class DrawPass {
this.marking.setSize(width, height);
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
this.bloom.setSize(width, height);
}
private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
@@ -225,7 +252,11 @@ export class DrawPass {
if (toDrawingBuffer) {
this.drawTarget.bind();
} else {
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
} else {
this.colorTarget.bind();
}
}
renderer.clear(true);
@@ -234,8 +265,21 @@ export class DrawPass {
}
if (!toDrawingBuffer) {
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetOpaque) {
this.depthTargetOpaque.bind();
renderer.clearDepth(true);
renderer.renderDepthOpaque(scene.primitives, camera, null);
this.colorTarget.bind();
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.depthTextureOpaque.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
if (!this.packedDepth) {
this.depthTextureOpaque.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
} else {
this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
}
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
@@ -247,19 +291,31 @@ export class DrawPass {
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
} else {
this.colorTarget.depthRenderbuffer?.attachFramebuffer(this.postprocessing.target.framebuffer);
}
}
if (scene.volumes.renderables.length > 0) {
const target = PostprocessingPass.isEnabled(postprocessingProps)
? this.postprocessing.target : this.colorTarget;
this.depthTextureOpaque.detachFramebuffer(target.framebuffer, 'depth');
if (!this.packedDepth) {
this.depthTextureOpaque.detachFramebuffer(target.framebuffer, 'depth');
} else {
this.colorTarget.depthRenderbuffer?.detachFramebuffer(target.framebuffer);
}
target.bind();
renderer.renderBlendedVolume(scene.volumes, camera, this.depthTextureOpaque);
this.depthTextureOpaque.attachFramebuffer(target.framebuffer, 'depth');
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(target.framebuffer, 'depth');
} else {
this.colorTarget.depthRenderbuffer?.attachFramebuffer(target.framebuffer);
}
target.bind();
}
}
@@ -333,7 +389,10 @@ export class DrawPass {
}
if (antialiasingEnabled) {
this.antialiasing.render(camera, toDrawingBuffer, props.postprocessing);
const input = PostprocessingPass.isEnabled(props.postprocessing)
? this.postprocessing.target.texture
: this.colorTarget.texture;
this.antialiasing.render(camera, input, toDrawingBuffer, props.postprocessing);
} else if (toDrawingBuffer) {
this.drawTarget.bind();
@@ -345,6 +404,22 @@ export class DrawPass {
}
}
if (props.postprocessing.bloom.name === 'on') {
const emissiveBloom = props.postprocessing.bloom.params.mode === 'emissive';
if (emissiveBloom && scene.emissiveAverage > 0) {
this.bloom.emissiveTarget.bind();
renderer.clear(false, true);
renderer.update(camera, scene);
renderer.renderEmissive(scene.primitives, camera, null);
}
if (!emissiveBloom || scene.emissiveAverage > 0) {
this.bloom.update(this.colorTarget.texture, this.bloom.emissiveTarget.texture, this.depthTargetOpaque?.texture || this.depthTextureOpaque, props.postprocessing.bloom.params);
this.bloom.render(camera.viewport, toDrawingBuffer ? undefined : this.getColorTarget(props.postprocessing));
}
}
this.webgl.gl.flush();
}

View File

@@ -11,6 +11,7 @@ import { isWebGL2 } from '../../mol-gl/webgl/compat';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
import { Texture } from '../../mol-gl/webgl/texture';
import { Vec3 } from '../../mol-math/linear-algebra';
import { spiral2d } from '../../mol-math/misc';
@@ -45,7 +46,7 @@ export class PickPass {
private readonly groupPickFramebuffer: Framebuffer;
private readonly depthPickFramebuffer: Framebuffer;
private readonly depthTexture: Texture;
private readonly depthRenderbuffer: Renderbuffer;
private pickWidth: number;
private pickHeight: number;
@@ -90,11 +91,11 @@ export class PickPass {
this.groupPickTexture.attachFramebuffer(this.framebuffer, 'color2');
this.depthPickTexture.attachFramebuffer(this.framebuffer, 'color3');
this.depthTexture = isWebGL2(gl)
? resources.texture('image-depth', 'depth', 'float', 'nearest')
: resources.texture('image-depth', 'depth', 'ushort', 'nearest');
this.depthTexture.define(this.pickWidth, this.pickHeight);
this.depthTexture.attachFramebuffer(this.framebuffer, 'depth');
this.depthRenderbuffer = isWebGL2(gl)
? resources.renderbuffer('depth32f', 'depth', this.pickWidth, this.pickHeight)
: resources.renderbuffer('depth16', 'depth', this.pickWidth, this.pickHeight);
this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
this.objectPickTexture.attachFramebuffer(this.objectPickFramebuffer, 'color0');
this.instancePickTexture.attachFramebuffer(this.instancePickFramebuffer, 'color0');
@@ -168,7 +169,7 @@ export class PickPass {
this.groupPickTexture.define(this.pickWidth, this.pickHeight);
this.depthPickTexture.define(this.pickWidth, this.pickHeight);
this.depthTexture.define(this.pickWidth, this.pickHeight);
this.depthRenderbuffer.setSize(this.pickWidth, this.pickHeight);
} else {
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 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>
@@ -34,8 +34,9 @@ import { AssetManager } from '../../mol-util/assets';
import { Light } from '../../mol-gl/renderer';
import { shadows_frag } from '../../mol-gl/shader/shadows.frag';
import { CasParams, CasPass } from './cas';
import { BloomParams } from './bloom';
const OutlinesSchema = {
export const OutlinesSchema = {
...QuadSchema,
tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
@@ -49,9 +50,9 @@ const OutlinesSchema = {
uOutlineThreshold: UniformSpec('f'),
dTransparentOutline: DefineSpec('boolean'),
};
type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
export type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
export function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
const width = depthTextureOpaque.getWidth();
const height = depthTextureOpaque.getHeight();
@@ -405,6 +406,10 @@ export const PostprocessingParams = {
off: PD.Group({})
}, { cycle: true, description: 'Contrast Adaptive Sharpening' }),
background: PD.Group(BackgroundParams, { isFlat: true }),
bloom: PD.MappedStatic('on', {
on: PD.Group(BloomParams),
off: PD.Group({})
}, { cycle: true, description: 'Bloom' }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
@@ -515,22 +520,28 @@ export class PostprocessingPass {
const qw = Math.max(1, Math.floor(sw * 0.25));
const qh = Math.max(1, Math.floor(sh * 0.25));
this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.downsampledDepthTarget = drawPass.packedDepth
? webgl.createRenderTarget(sw, sh, false, 'uint8', 'nearest', 'rgba')
: webgl.createRenderTarget(sw, sh, false, 'float32', 'nearest', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
const depthTexture = this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture;
this.depthHalfTarget = webgl.createRenderTarget(hw, hh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthHalfTarget = drawPass.packedDepth
? webgl.createRenderTarget(hw, hh, false, 'uint8', 'nearest', 'rgba')
: webgl.createRenderTarget(hw, hh, false, 'float32', 'nearest', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthHalfRenderable = createCopyRenderable(webgl, depthTexture);
this.depthQuarterTarget = webgl.createRenderTarget(qw, qh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthQuarterTarget = drawPass.packedDepth
? webgl.createRenderTarget(qw, qh, false, 'uint8', 'nearest', 'rgba')
: webgl.createRenderTarget(qw, qh, false, 'float32', 'nearest', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthQuarterRenderable = createCopyRenderable(webgl, this.depthHalfTarget.texture);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthBlurProxyTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
@@ -590,7 +601,7 @@ export class PostprocessingPass {
}
}
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
let needsUpdateShadows = false;
let needsUpdateMain = false;
let needsUpdateSsao = false;
@@ -958,11 +969,7 @@ export class AntialiasingPass {
private readonly smaa: SmaaPass;
private readonly cas: CasPass;
constructor(webgl: WebGLContext, private drawPass: DrawPass) {
const { colorTarget } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
constructor(webgl: WebGLContext, width: number, height: number) {
this.target = webgl.createRenderTarget(width, height, false);
this.internalTarget = webgl.createRenderTarget(width, height, false);
@@ -984,47 +991,37 @@ export class AntialiasingPass {
}
}
private _renderFxaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
private _renderFxaa(camera: ICamera, input: Texture, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.antialiasing.name !== 'fxaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.fxaa.update(input, props.antialiasing.params);
this.fxaa.render(camera.viewport, target);
}
private _renderSmaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
private _renderSmaa(camera: ICamera, input: Texture, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.antialiasing.name !== 'smaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.smaa.update(input, props.antialiasing.params);
this.smaa.render(camera.viewport, target);
}
private _renderAntialiasing(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
private _renderAntialiasing(camera: ICamera, input: Texture, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.antialiasing.name === 'fxaa') {
this._renderFxaa(camera, target, props);
this._renderFxaa(camera, input, target, props);
} else if (props.antialiasing.name === 'smaa') {
this._renderSmaa(camera, target, props);
this._renderSmaa(camera, input, target, props);
}
}
private _renderCas(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
private _renderCas(camera: ICamera, input: Texture, target: RenderTarget | undefined, props: PostprocessingProps) {
if (props.sharpening.name !== 'on') return;
const input = props.antialiasing.name !== 'off'
? this.internalTarget.texture
: PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
if (props.antialiasing.name !== 'off') input = this.internalTarget.texture;
this.cas.update(input, props.sharpening.params);
this.cas.render(camera.viewport, target);
}
render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
render(camera: ICamera, input: Texture, toDrawingBuffer: boolean | RenderTarget, props: PostprocessingProps) {
if (props.antialiasing.name === 'off' && props.sharpening.name === 'off') return;
if (props.antialiasing.name === 'smaa' && !this.smaa.supported) {
@@ -1032,14 +1029,16 @@ export class AntialiasingPass {
return;
}
const target = toDrawingBuffer ? undefined : this.target;
const target = toDrawingBuffer === true
? undefined : toDrawingBuffer === false
? this.target : toDrawingBuffer;
if (props.sharpening.name === 'off') {
this._renderAntialiasing(camera, target, props);
this._renderAntialiasing(camera, input, target, props);
} else if (props.antialiasing.name === 'off') {
this._renderCas(camera, target, props);
this._renderCas(camera, input, target, props);
} else {
this._renderAntialiasing(camera, this.internalTarget, props);
this._renderCas(camera, target, props);
this._renderAntialiasing(camera, input, this.internalTarget, props);
this._renderCas(camera, input, target, props);
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -19,6 +19,7 @@ import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { Vec2 } from '../../mol-math/linear-algebra';
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
import { isWebGL2 } from '../../mol-gl/webgl/compat';
import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
const EvaluateWboitSchema = {
...QuadSchema,
@@ -51,7 +52,7 @@ export class WboitPass {
private readonly framebuffer: Framebuffer;
private readonly textureA: Texture;
private readonly textureB: Texture;
private readonly depthTexture: Texture;
private readonly depthRenderbuffer: Renderbuffer;
private _supported = false;
get supported() {
@@ -89,7 +90,7 @@ export class WboitPass {
if (width !== w || height !== h) {
this.textureA.define(width, height);
this.textureB.define(width, height);
this.depthTexture.define(width, height);
this.depthRenderbuffer.setSize(width, height);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
}
}
@@ -109,16 +110,18 @@ export class WboitPass {
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
this.depthTexture.attachFramebuffer(this.framebuffer, 'depth');
this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
}
static isSupported(webgl: WebGLContext) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat } } = webgl;
if (!textureFloat || !colorBufferFloat || !drawBuffers) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, depthTexture } } = webgl;
if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers) {
if (isDebugMode) {
const missing: string[] = [];
if (!textureFloat) missing.push('textureFloat');
if (!colorBufferFloat) missing.push('colorBufferFloat');
if (!depthTexture) missing.push('depthTexture');
if (!drawBuffers) missing.push('drawBuffers');
console.log(`Missing "${missing.join('", "')}" extensions required for "wboit"`);
}
@@ -139,10 +142,9 @@ export class WboitPass {
this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest');
this.textureB.define(width, height);
this.depthTexture = isWebGL2(gl)
? resources.texture('image-depth', 'depth', 'float', 'nearest')
: resources.texture('image-depth', 'depth', 'ushort', 'nearest');
this.depthTexture.define(width, height);
this.depthRenderbuffer = isWebGL2(gl)
? resources.renderbuffer('depth32f', 'depth', width, height)
: resources.renderbuffer('depth16', 'depth', width, height);
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
this.framebuffer = resources.framebuffer();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -86,6 +86,7 @@ export namespace BaseGeometry {
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
material: Material.getParam(),
clip: PD.Group(Clip.Params),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
instanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }),
lod: PD.Vec3(Vec3(), undefined, { ...CullingLodCategory, description: 'Level of detail.', fieldLabels: { x: 'Min Distance', y: 'Max Distance', z: 'Overlap (Shader)' } }),
cellSize: PD.Numeric(200, { min: 0, max: 5000, step: 100 }, { ...CullingLodCategory, description: 'Instance grid cell size.' }),
@@ -116,6 +117,7 @@ export namespace BaseGeometry {
uMetalness: ValueCell.create(props.material.metalness),
uRoughness: ValueCell.create(props.material.roughness),
uBumpiness: ValueCell.create(props.material.bumpiness),
uEmissive: ValueCell.create(props.emissive),
dLightCount: ValueCell.create(1),
dColorMarker: ValueCell.create(true),
@@ -137,6 +139,7 @@ export namespace BaseGeometry {
ValueCell.updateIfChanged(values.uMetalness, props.material.metalness);
ValueCell.updateIfChanged(values.uRoughness, props.material.roughness);
ValueCell.updateIfChanged(values.uBumpiness, props.material.bumpiness);
ValueCell.updateIfChanged(values.uEmissive, props.emissive);
const clip = Clip.getClip(props.clip);
ValueCell.updateIfChanged(values.dClipObjectCount, clip.objects.count);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -27,6 +27,7 @@ import { createEmptyClipping } from '../clipping-data';
import { CylindersValues } from '../../../mol-gl/renderable/cylinders';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
export interface Cylinders {
readonly kind: 'cylinders',
@@ -219,6 +220,7 @@ export namespace Cylinders {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -246,6 +248,7 @@ export namespace Cylinders {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -28,6 +28,7 @@ import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './
import { createEmptyClipping } from '../clipping-data';
import { Grid } from '../../../mol-model/volume';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
const VolumeBox = Box();
@@ -216,6 +217,7 @@ export namespace DirectVolume {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -235,6 +237,7 @@ export namespace DirectVolume {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../mol-util/value-cell';
import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
export type EmissiveType = 'instance' | 'groupInstance' | 'volumeInstance';
export type EmissiveData = {
tEmissive: ValueCell<TextureImage<Uint8Array>>
uEmissiveTexDim: ValueCell<Vec2>
dEmissive: ValueCell<boolean>,
emissiveAverage: ValueCell<number>,
tEmissiveGrid: ValueCell<Texture>,
uEmissiveGridDim: ValueCell<Vec3>,
uEmissiveGridTransform: ValueCell<Vec4>,
dEmissiveType: ValueCell<string>,
uEmissiveStrength: ValueCell<number>,
}
export function applyEmissiveValue(array: Uint8Array, start: number, end: number, value: number) {
for (let i = start; i < end; ++i) {
array[i] = value * 255;
}
return true;
}
export function getEmissiveAverage(array: Uint8Array, count: number): number {
if (count === 0 || array.length < count) return 0;
let sum = 0;
for (let i = 0; i < count; ++i) {
sum += array[i];
}
return sum / (255 * count);
}
export function clearEmissive(array: Uint8Array, start: number, end: number) {
array.fill(0, start, end);
}
export function createEmissive(count: number, type: EmissiveType, emissiveData?: EmissiveData): EmissiveData {
const emissive = createTextureImage(Math.max(1, count), 1, Uint8Array, emissiveData && emissiveData.tEmissive.ref.value.array);
if (emissiveData) {
ValueCell.update(emissiveData.tEmissive, emissive);
ValueCell.update(emissiveData.uEmissiveTexDim, Vec2.create(emissive.width, emissive.height));
ValueCell.updateIfChanged(emissiveData.dEmissive, count > 0);
ValueCell.updateIfChanged(emissiveData.emissiveAverage, getEmissiveAverage(emissive.array, count));
ValueCell.updateIfChanged(emissiveData.dEmissiveType, type);
return emissiveData;
} else {
return {
tEmissive: ValueCell.create(emissive),
uEmissiveTexDim: ValueCell.create(Vec2.create(emissive.width, emissive.height)),
dEmissive: ValueCell.create(count > 0),
emissiveAverage: ValueCell.create(0),
tEmissiveGrid: ValueCell.create(createNullTexture()),
uEmissiveGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uEmissiveGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dEmissiveType: ValueCell.create(type),
uEmissiveStrength: ValueCell.create(1),
};
}
}
const emptyEmissiveTexture = { array: new Uint8Array(1), width: 1, height: 1 };
export function createEmptyEmissive(emissiveData?: EmissiveData): EmissiveData {
if (emissiveData) {
ValueCell.update(emissiveData.tEmissive, emptyEmissiveTexture);
ValueCell.update(emissiveData.uEmissiveTexDim, Vec2.create(1, 1));
return emissiveData;
} else {
return {
tEmissive: ValueCell.create(emptyEmissiveTexture),
uEmissiveTexDim: ValueCell.create(Vec2.create(1, 1)),
dEmissive: ValueCell.create(false),
emissiveAverage: ValueCell.create(0),
tEmissiveGrid: ValueCell.create(createNullTexture()),
uEmissiveGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uEmissiveGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dEmissiveType: ValueCell.create('groupInstance'),
uEmissiveStrength: ValueCell.create(1),
};
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -27,6 +27,7 @@ import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { QuadPositions } from '../../../mol-gl/compute/util';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
const QuadIndices = new Uint32Array([
0, 1, 2,
@@ -148,6 +149,7 @@ namespace Image {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -163,6 +165,7 @@ namespace Image {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -27,6 +27,7 @@ import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
/** Wide line */
export interface Lines {
@@ -213,6 +214,7 @@ export namespace Lines {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -237,6 +239,7 @@ export namespace Lines {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -359,6 +359,40 @@ export function applyMeshTransparencySmoothing(values: MeshValues, resolution: n
}
}
function isSupportedEmissiveType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyMeshEmissiveSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
if (!isSupportedEmissiveType(values.dEmissiveType.ref.value)) return;
const smoothingData = calcMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionBuffer: values.aPosition.ref.value,
groupBuffer: values.aGroup.ref.value,
colorData: values.tEmissive.ref.value,
colorType: values.dEmissiveType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
itemSize: 1
}, resolution, stride, webgl, colorTexture);
if (smoothingData.kind === 'volume') {
ValueCell.updateIfChanged(values.dEmissiveType, smoothingData.type);
ValueCell.update(values.tEmissiveGrid, smoothingData.texture);
ValueCell.update(values.uEmissiveTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uEmissiveGridDim, smoothingData.gridDim);
ValueCell.update(values.uEmissiveGridTransform, smoothingData.gridTransform);
} else if (smoothingData.kind === 'vertex') {
ValueCell.updateIfChanged(values.dEmissiveType, smoothingData.type);
ValueCell.update(values.tEmissive, smoothingData.texture);
ValueCell.update(values.uEmissiveTexDim, smoothingData.texDim);
}
}
function isSupportedSubstanceType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 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>
@@ -28,6 +28,7 @@ import { RenderableState } from '../../../mol-gl/renderable';
import { arraySetAdd } from '../../../mol-util/array';
import { degToRad } from '../../../mol-math/misc';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
export interface Mesh {
readonly kind: 'mesh',
@@ -651,13 +652,17 @@ export namespace Mesh {
const instanceCount = transform.instanceCount.ref.value;
const location = PositionLocation();
const p = location.position;
const v = mesh.vertexBuffer.ref.value;
const n = location.normal;
const vs = mesh.vertexBuffer.ref.value;
const ns = mesh.normalBuffer.ref.value;
const m = transform.aTransform.ref.value;
const getLocation = (groupIndex: number, instanceIndex: number) => {
if (instanceIndex < 0) {
Vec3.fromArray(p, v, groupIndex * 3);
Vec3.fromArray(p, vs, groupIndex * 3);
Vec3.fromArray(n, ns, groupIndex * 3);
} else {
Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
Vec3.transformMat4Offset(p, vs, m, 0, groupIndex * 3, instanceIndex * 16);
Vec3.transformDirectionOffset(n, ns, m, 0, groupIndex * 3, instanceIndex * 16);
}
return location;
};
@@ -674,6 +679,7 @@ export namespace Mesh {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -697,6 +703,7 @@ export namespace Mesh {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -26,6 +26,7 @@ import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
/** Point cloud */
export interface Points {
@@ -175,6 +176,7 @@ export namespace Points {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -196,6 +198,7 @@ export namespace Points {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -26,6 +26,7 @@ import { createEmptyClipping } from '../clipping-data';
import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
export interface Spheres {
readonly kind: 'spheres',
@@ -97,7 +98,7 @@ export namespace Spheres {
get boundingSphere() {
const newHash = hashCode(spheres);
if (newHash !== currentHash) {
const b = calculateInvariantBoundingSphere(spheres.centerBuffer.ref.value, spheres.sphereCount * 4, 4);
const b = calculateInvariantBoundingSphere(spheres.centerBuffer.ref.value, spheres.sphereCount, 1);
Sphere3D.copy(boundingSphere, b);
currentHash = newHash;
}
@@ -105,7 +106,7 @@ export namespace Spheres {
},
get groupMapping() {
if (spheres.groupBuffer.ref.version !== currentGroup) {
groupMapping = createGroupMapping(spheres.groupBuffer.ref.value, spheres.sphereCount, 4);
groupMapping = createGroupMapping(spheres.groupBuffer.ref.value, spheres.sphereCount);
currentGroup = spheres.groupBuffer.ref.version;
}
return groupMapping;
@@ -135,6 +136,7 @@ export namespace Spheres {
}
},
};
spheres.shaderData.update();
return spheres;
}
@@ -307,6 +309,7 @@ export namespace Spheres {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -332,6 +335,7 @@ export namespace Spheres {
...marker,
...overpaint,
...transparency,
...emissive,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -30,6 +30,7 @@ import { hashFnv32a } from '../../../mol-data/util';
import { GroupMapping, createGroupMapping } from '../../util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
import { createEmptyEmissive } from '../emissive-data';
type TextAttachment = (
'bottom-left' | 'bottom-center' | 'bottom-right' |
@@ -216,6 +217,7 @@ export namespace Text {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const substance = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -241,6 +243,7 @@ export namespace Text {
...marker,
...overpaint,
...transparency,
...emissive,
...substance,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -508,6 +508,42 @@ export function applyTextureMeshTransparencySmoothing(values: TextureMeshValues,
ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
}
function isSupportedEmissiveType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyTextureMeshEmissiveSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
if (!isSupportedEmissiveType(values.dEmissiveType.ref.value)) return;
stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
if (!webgl.namedTextures[ColorSmoothingAlphaName]) {
webgl.namedTextures[ColorSmoothingAlphaName] = webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'nearest');
}
const colorData = webgl.namedTextures[ColorSmoothingAlphaName];
colorData.load(values.tEmissive.ref.value);
const smoothingData = calcTextureMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionTexture: values.tPosition.ref.value,
groupTexture: values.tGroup.ref.value,
colorData,
colorType: values.dEmissiveType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
}, resolution, stride, webgl, colorTexture);
ValueCell.updateIfChanged(values.dEmissiveType, smoothingData.type);
ValueCell.update(values.tEmissiveGrid, smoothingData.texture);
ValueCell.update(values.uEmissiveTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uEmissiveGridDim, smoothingData.gridDim);
ValueCell.update(values.uEmissiveGridTransform, smoothingData.gridTransform);
}
function isSupportedSubstanceType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Cai Huiyu <szmun.caihy@gmail.com>
*/
import { ValueCell } from '../../../mol-util';
@@ -26,6 +27,7 @@ import { NullLocation } from '../../../mol-model/location';
import { createEmptySubstance } from '../substance-data';
import { RenderableState } from '../../../mol-gl/renderable';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { createEmptyEmissive } from '../emissive-data';
export interface TextureMesh {
readonly kind: 'texture-mesh',
@@ -154,17 +156,24 @@ export namespace TextureMesh {
textureMesh.vertexTexture.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, vertices);
const normals = new Float32Array(width * height * 4);
framebuffer.bind();
textureMesh.normalTexture.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, normals);
const groupCount = textureMesh.vertexCount;
const instanceCount = transform.instanceCount.ref.value;
const location = PositionLocation();
const p = location.position;
const v = vertices;
const n = location.normal;
const m = transform.aTransform.ref.value;
const getLocation = (groupIndex: number, instanceIndex: number) => {
if (instanceIndex < 0) {
Vec3.fromArray(p, v, groupIndex * 4);
Vec3.fromArray(p, vertices, groupIndex * 4);
Vec3.fromArray(n, normals, groupIndex * 4);
} else {
Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 4, instanceIndex * 16);
Vec3.transformMat4Offset(p, vertices, m, 0, groupIndex * 4, instanceIndex * 16);
Vec3.transformDirectionOffset(n, normals, m, 0, groupIndex * 4, instanceIndex * 16);
}
return location;
};
@@ -181,6 +190,7 @@ export namespace TextureMesh {
: createMarkers(instanceCount * groupCount, 'groupInstance');
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const emissive = createEmptyEmissive();
const substance = createEmptySubstance();
const clipping = createEmptyClipping();
@@ -206,6 +216,7 @@ export namespace TextureMesh {
...marker,
...overpaint,
...transparency,
...emissive,
...substance,
...clipping,
...transform,

View File

@@ -1,8 +1,9 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
* @author Cai Huiyu <szmun.caihy@gmail.com>
*/
import { Iterator } from '../../mol-data';
@@ -119,10 +120,16 @@ export function LocationIterator(groupCount: number, instanceCount: number, stri
/** A position Location */
export interface PositionLocation {
readonly kind: 'position-location',
readonly position: Vec3
readonly position: Vec3,
/** Normal vector at the position (used for surface coloring) */
readonly normal: Vec3
}
export function PositionLocation(position?: Vec3): PositionLocation {
return { kind: 'position-location', position: position ? Vec3.clone(position) : Vec3() };
export function PositionLocation(position?: Vec3, normal?: Vec3): PositionLocation {
return {
kind: 'position-location',
position: position ? Vec3.clone(position) : Vec3(),
normal: normal ? Vec3.clone(normal) : Vec3()
};
}
export function isPositionLocation(x: any): x is PositionLocation {
return !!x && x.kind === 'position-location';

View File

@@ -203,6 +203,7 @@ const gl = {
RGB565: 36194,
RGBA: 6408,
RGBA4: 32854,
RGBA8: 32856,
SAMPLER_2D: 35678,
SAMPLER_CUBE: 35680,
SAMPLES: 32937,

View File

@@ -52,18 +52,18 @@ describe('renderer', () => {
scene.add(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(9);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 4 : 0);
expect(ctx.stats.resourceCounts.program).toBe(4);
expect(ctx.stats.resourceCounts.shader).toBe(8);
expect(ctx.stats.resourceCounts.texture).toBe(10);
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);
scene.remove(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(0);
expect(ctx.stats.resourceCounts.texture).toBe(1);
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
expect(ctx.stats.resourceCounts.program).toBe(4);
expect(ctx.stats.resourceCounts.shader).toBe(8);
expect(ctx.stats.resourceCounts.program).toBe(5);
expect(ctx.stats.resourceCounts.shader).toBe(10);
ctx.resources.destroy();
expect(ctx.stats.resourceCounts.program).toBe(0);
@@ -89,9 +89,9 @@ describe('renderer', () => {
sceneDpoit.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 12 : 15);
expect(ctx.stats.resourceCounts.texture).toBe(25);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 12 : 0);
expect(ctx.stats.resourceCounts.program).toBe(6);
expect(ctx.stats.resourceCounts.shader).toBe(12);
expect(ctx.stats.resourceCounts.texture).toBe(28);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 15 : 0);
expect(ctx.stats.resourceCounts.program).toBe(7);
expect(ctx.stats.resourceCounts.shader).toBe(14);
});
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -114,7 +114,7 @@ export function createRenderable<T extends GraphicsRenderableValues>(renderItem:
if (values.drawCount.ref.value === 0) return;
if (values.instanceCount.ref.value === 0) return;
if (!values.instanceGrid.ref.value) return;
if (values.instanceGrid.ref.value.cellSize <= 1) return;
const { cellOffsets, cellSpheres, cellCount, batchOffsets, batchSpheres, batchCount, batchCell, batchSize } = values.instanceGrid.ref.value;
const [minDistance, maxDistance] = values.uLod.ref.value;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -262,6 +262,21 @@ export const TransparencySchema = {
export type TransparencySchema = typeof TransparencySchema
export type TransparencyValues = Values<TransparencySchema>
export const EmissiveSchema = {
uEmissiveTexDim: UniformSpec('v2'),
tEmissive: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
dEmissive: DefineSpec('boolean'),
emissiveAverage: ValueSpec('number'),
uEmissiveGridDim: UniformSpec('v3'),
uEmissiveGridTransform: UniformSpec('v4'),
tEmissiveGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
dEmissiveType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uEmissiveStrength: UniformSpec('f', 'material'),
} as const;
export type EmissiveSchema = typeof EmissiveSchema
export type EmissiveValues = Values<EmissiveSchema>
export const SubstanceSchema = {
uSubstanceTexDim: UniformSpec('v2'),
tSubstance: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
@@ -292,6 +307,7 @@ export const BaseSchema = {
...MarkerSchema,
...OverpaintSchema,
...TransparencySchema,
...EmissiveSchema,
...SubstanceSchema,
...ClippingSchema,
@@ -320,6 +336,7 @@ export const BaseSchema = {
uMetalness: UniformSpec('f', 'material'),
uRoughness: UniformSpec('f', 'material'),
uBumpiness: UniformSpec('f', 'material'),
uEmissive: UniformSpec('f', 'material'),
uVertexCount: UniformSpec('i'),
uInstanceCount: UniformSpec('i'),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -32,6 +32,7 @@ export interface RendererStats {
attributeCount: number
elementsCount: number
framebufferCount: number
renderbufferCount: number
textureCount: number
vertexArrayCount: number
@@ -68,6 +69,7 @@ interface Renderer {
renderDepthTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderEmissive: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlended: (group: Scene, camera: ICamera) => void
renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
@@ -280,7 +282,8 @@ namespace Renderer {
if (d - radius > maxDistance) return;
}
if (r.values.instanceGrid.ref.value.cellSize > 1 || r.values.lodLevels) {
const hasInstanceGrid = r.values.instanceGrid.ref.value.cellSize > 1;
if (hasInstanceGrid || (hasInstanceGrid && r.values.lodLevels)) {
r.cull(cameraPlane, frustum, isOccluded, ctx.stats);
} else {
r.uncull();
@@ -419,6 +422,35 @@ namespace Renderer {
state.currentRenderItemId = -1;
};
const checkOpaque = function (r: GraphicsRenderable) {
// uAlpha is updated in `r.render` so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
return (
(alpha === 1 &&
r.values.transparencyAverage.ref.value !== 1 &&
r.values.dGeometryType.ref.value !== 'directVolume' &&
r.values.dPointStyle?.ref.value !== 'fuzzy' &&
!xrayShaded
) || r.values.dTransparentBackfaces?.ref.value === 'opaque'
);
};
const checkTransparent = function (r: GraphicsRenderable) {
// uAlpha is updated in `r.render` so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
return (
(alpha < 1 && alpha !== 0) ||
r.values.transparencyAverage.ref.value > 0 ||
r.values.dGeometryType.ref.value === 'directVolume' ||
r.values.dPointStyle?.ref.value === 'fuzzy' ||
r.values.dGeometryType.ref.value === 'text' ||
r.values.dGeometryType.ref.value === 'image' ||
xrayShaded
);
};
const renderPick = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null, pickType: PickType) => {
if (isTimingMode) ctx.timer.mark('Renderer.renderPick');
state.disable(gl.BLEND);
@@ -463,8 +495,7 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && !xrayShaded) {
if (checkOpaque(r)) {
renderObject(r, 'depth', Flag.None);
}
}
@@ -482,8 +513,7 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || xrayShaded) {
if (checkTransparent(r)) {
renderObject(r, 'depth', Flag.None);
}
}
@@ -504,7 +534,7 @@ namespace Renderer {
const r = renderables[i];
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha !== 0 && r.values.markerAverage.ref.value !== 1) {
if (alpha !== 0 && r.values.transparencyAverage.ref.value !== 1 && r.values.markerAverage.ref.value !== 1) {
renderObject(renderables[i], 'marking', Flag.None);
}
}
@@ -531,6 +561,24 @@ namespace Renderer {
if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingMask');
};
const renderEmissive = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
if (isTimingMode) ctx.timer.mark('Renderer.renderEmissive');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, depthTexture, Mask.Opaque, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkOpaque(r)) {
renderObject(r, 'emissive', Flag.None);
}
}
if (isTimingMode) ctx.timer.markEnd('Renderer.renderEmissive');
};
const renderBlended = (scene: Scene, camera: ICamera) => {
if (scene.hasOpaque) {
renderBlendedOpaque(scene, camera, null);
@@ -551,10 +599,8 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (r.state.opaque) {
if (checkOpaque(r)) {
renderObject(r, 'color', Flag.None);
} else if (r.values.uDoubleSided?.ref.value && r.values.dTransparentBackfaces?.ref.value === 'opaque') {
renderObject(r, 'color', Flag.BlendedBack);
}
}
if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedOpaque');
@@ -562,31 +608,21 @@ namespace Renderer {
const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedTransparent');
state.enable(gl.DEPTH_TEST);
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
const { renderables } = group;
if (transparentBackground) {
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
} else {
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
}
state.enable(gl.BLEND);
state.depthMask(true);
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (!r.state.opaque && r.state.writeDepth) {
renderObject(r, 'color', Flag.None);
}
}
state.enable(gl.DEPTH_TEST);
state.depthMask(false);
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if ((!r.state.opaque && !r.state.writeDepth) || r.values.transparencyAverage.ref.value > 0) {
if (checkTransparent(r)) {
if (r.values.uDoubleSided?.ref.value) {
// render frontfaces and backfaces separately to avoid artefacts
if (r.values.dTransparentBackfaces?.ref.value !== 'opaque') {
@@ -605,6 +641,7 @@ namespace Renderer {
if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedVolume');
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
state.depthMask(false);
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
@@ -629,12 +666,7 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !xrayShaded) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
if (checkOpaque(r)) {
renderObject(r, 'color', Flag.None);
}
}
@@ -648,12 +680,7 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || xrayShaded) {
if (checkTransparent(r)) {
renderObject(r, 'color', Flag.None);
}
}
@@ -671,12 +698,7 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !xrayShaded) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
if (checkOpaque(r)) {
renderObject(r, 'color', Flag.None);
}
}
@@ -698,12 +720,7 @@ namespace Renderer {
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || xrayShaded) {
if (checkTransparent(r)) {
renderObject(r, 'color', Flag.None);
}
}
@@ -764,6 +781,7 @@ namespace Renderer {
renderDepthTransparent,
renderMarkingDepth,
renderMarkingMask,
renderEmissive,
renderBlended,
renderBlendedOpaque,
renderBlendedTransparent,
@@ -889,6 +907,7 @@ namespace Renderer {
attributeCount: ctx.stats.resourceCounts.attribute,
elementsCount: ctx.stats.resourceCounts.elements,
framebufferCount: ctx.stats.resourceCounts.framebuffer,
renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
textureCount: ctx.stats.resourceCounts.texture,
vertexArrayCount: ctx.stats.resourceCounts.vertexArray,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 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>
@@ -84,6 +84,8 @@ interface Scene extends Object3D {
forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
/** Marker average of primitive renderables */
readonly markerAverage: number
/** Emissive average of primitive renderables */
readonly emissiveAverage: number
/** Opacity average of primitive renderables */
readonly opacityAverage: number
/** Is `true` if any primitive renderable (possibly) has any opaque part */
@@ -108,10 +110,12 @@ namespace Scene {
let boundingSphereVisibleDirty = true;
let markerAverageDirty = true;
let emissiveAverageDirty = true;
let opacityAverageDirty = true;
let hasOpaqueDirty = true;
let markerAverage = 0;
let emissiveAverage = 0;
let opacityAverage = 0;
let hasOpaque = false;
@@ -170,6 +174,7 @@ namespace Scene {
renderables.sort(renderableSort);
markerAverageDirty = true;
emissiveAverageDirty = true;
opacityAverageDirty = true;
hasOpaqueDirty = true;
return true;
@@ -194,6 +199,7 @@ namespace Scene {
if (newVisibleHash !== visibleHash) {
boundingSphereVisibleDirty = true;
markerAverageDirty = true;
emissiveAverageDirty = true;
opacityAverageDirty = true;
hasOpaqueDirty = true;
visibleHash = newVisibleHash;
@@ -215,6 +221,18 @@ namespace Scene {
return count > 0 ? markerAverage / count : 0;
}
function calculateEmissiveAverage() {
if (primitives.length === 0) return 0;
let count = 0;
let emissiveAverage = 0;
for (let i = 0, il = primitives.length; i < il; ++i) {
if (!primitives[i].state.visible) continue;
emissiveAverage += primitives[i].values.emissiveAverage.ref.value + primitives[i].values.uEmissive.ref.value;
count += 1;
}
return count > 0 ? emissiveAverage / count : 0;
}
function calculateOpacityAverage() {
if (primitives.length === 0) return 0;
let count = 0;
@@ -279,6 +297,7 @@ namespace Scene {
syncVisibility();
}
markerAverageDirty = true;
emissiveAverageDirty = true;
opacityAverageDirty = true;
hasOpaqueDirty = true;
},
@@ -328,6 +347,13 @@ namespace Scene {
}
return markerAverage;
},
get emissiveAverage() {
if (emissiveAverageDirty) {
emissiveAverage = calculateEmissiveAverage();
emissiveAverageDirty = false;
}
return emissiveAverage;
},
get opacityAverage() {
if (opacityAverageDirty) {
opacityAverage = calculateOpacityAverage();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -53,6 +53,7 @@ import { assign_material_color } from './shader/chunks/assign-material-color.gls
import { assign_position } from './shader/chunks/assign-position.glsl';
import { assign_size } from './shader/chunks/assign-size.glsl';
import { check_picking_alpha } from './shader/chunks/check-picking-alpha.glsl';
import { check_transparency } from './shader/chunks/check-transparency.glsl';
import { clip_instance } from './shader/chunks/clip-instance.glsl';
import { clip_pixel } from './shader/chunks/clip-pixel.glsl';
import { color_frag_params } from './shader/chunks/color-frag-params.glsl';
@@ -88,6 +89,7 @@ const ShaderChunks: { [k: string]: string } = {
assign_position,
assign_size,
check_picking_alpha,
check_transparency,
clip_instance,
clip_pixel,
color_frag_params,
@@ -166,13 +168,17 @@ function ignoreDefine(name: string, variant: string, defines: ShaderDefines): bo
return !!defines.dIgnoreLight?.ref.value;
}
} else {
return [
const ignore = [
'dColorType', 'dUsePalette',
'dLightCount', 'dXrayShaded',
'dOverpaintType', 'dOverpaint',
'dSubstanceType', 'dSubstance',
'dColorMarker',
].includes(name);
];
if (variant !== 'emissive') {
ignore.push('dEmissiveType', 'dEmissive');
}
return ignore.includes(name);
}
return false;
};
@@ -294,6 +300,9 @@ function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: Sha
throw new Error(`required 'GL_EXT_shader_texture_lod' extension not available`);
}
}
if (extensions.depthTexture) {
prefix.push('#define depthTextureSupport');
}
return prefix.join('\n') + '\n';
}
@@ -312,6 +321,8 @@ const glsl300FragPrefixCommon = `
#define gl_FragColor out_FragData0
#define gl_FragDepthEXT gl_FragDepth
#define depthTextureSupport
`;
function getGlsl300VertPrefix(extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {

View File

@@ -10,6 +10,7 @@ precision mediump sampler2D;
uniform float uOpacity;
uniform float uSaturation;
uniform float uLightness;
uniform mat3 uRotation;
#elif defined(dVariant_image)
uniform sampler2D tImage;
uniform vec2 uImageScale;
@@ -52,9 +53,9 @@ void main() {
#if defined(dVariant_skybox)
vec4 t = uViewDirectionProjectionInverse * vPosition;
#ifdef enabledShaderTextureLod
gl_FragColor = textureCubeLodEXT(tSkybox, normalize(t.xyz / t.w), uBlur * 8.0);
gl_FragColor = textureCubeLodEXT(tSkybox, uRotation * normalize(t.xyz / t.w), uBlur * 8.0);
#else
gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w));
gl_FragColor = textureCube(tSkybox, uRotation * normalize(t.xyz / t.w));
#endif
gl_FragColor.a = uOpacity;
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);

View File

@@ -0,0 +1,29 @@
export const blur_frag = `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform sampler2D tInput;
uniform vec2 uTexSizeInv;
uniform vec2 uDirection;
uniform float uGaussianCoefficients[dKernelRadius];
void main(void) {
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
float weightSum = uGaussianCoefficients[0];
vec4 diffuseSum = texture2D(tInput, coords) * weightSum;
for(int i = 1; i < dKernelRadius; ++i) {
float x = float(i);
float w = uGaussianCoefficients[i];
vec2 offset = uDirection * uTexSizeInv * x;
vec4 sample1 = texture2D(tInput, coords + offset);
vec4 sample2 = texture2D(tInput, coords - offset);
diffuseSum += (sample1 + sample2) * w;
weightSum += 2.0 * w;
}
gl_FragColor = diffuseSum / weightSum;
}
`;

View File

@@ -0,0 +1,34 @@
export const composite_frag = `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform vec2 uTexSizeInv;
uniform sampler2D tBlur1;
uniform sampler2D tBlur2;
uniform sampler2D tBlur3;
uniform sampler2D tBlur4;
uniform sampler2D tBlur5;
uniform float uBloomStrength;
uniform float uBloomRadius;
uniform float uBloomFactors[5];
uniform vec3 uBloomTints[5];
float lerpBloomFactor(const in float factor) {
float mirrorFactor = 1.2 - factor;
return mix(factor, mirrorFactor, uBloomRadius);
}
void main(void) {
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
gl_FragColor = uBloomStrength * (
lerpBloomFactor(uBloomFactors[0]) * vec4(uBloomTints[0], 1.0) * texture2D(tBlur1, coords) +
lerpBloomFactor(uBloomFactors[1]) * vec4(uBloomTints[1], 1.0) * texture2D(tBlur2, coords) +
lerpBloomFactor(uBloomFactors[2]) * vec4(uBloomTints[2], 1.0) * texture2D(tBlur3, coords) +
lerpBloomFactor(uBloomFactors[3]) * vec4(uBloomTints[3], 1.0) * texture2D(tBlur4, coords) +
lerpBloomFactor(uBloomFactors[4]) * vec4(uBloomTints[4], 1.0) * texture2D(tBlur5, coords)
);
}
`;

View File

@@ -0,0 +1,53 @@
export const luminosity_frag = `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform sampler2D tColor;
uniform sampler2D tEmissive;
uniform sampler2D tDepth;
uniform vec2 uTexSizeInv;
uniform vec3 uDefaultColor;
uniform float uDefaultOpacity;
uniform float uLuminosityThreshold;
uniform float uSmoothWidth;
#include common
float getDepth(const in vec2 coords) {
#ifdef depthTextureSupport
return texture2D(tDepth, coords).r;
#else
return unpackRGBAToDepth(texture2D(tDepth, coords));
#endif
}
bool isBackground(const in float depth) {
return depth == 1.0;
}
void main(void) {
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
vec4 texel = texture2D(tColor, coords);
float emissive = texture2D(tEmissive, coords).a;
float depth = getDepth(coords);
if (isBackground(depth)) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
vec4 outputColor = vec4(uDefaultColor.rgb, uDefaultOpacity);
#if defined(dMode_luminosity)
vec3 luma = vec3(0.299, 0.587, 0.114);
float v = dot(texel.xyz, luma);
float alpha = smoothstep(uLuminosityThreshold, uLuminosityThreshold + uSmoothWidth, v);
gl_FragColor = mix(outputColor, texel, alpha);
#elif defined(dMode_emissive)
gl_FragColor = mix(outputColor, texel, emissive);
#endif
}
`;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
@@ -8,7 +8,7 @@
*/
export const apply_light_color = `
#ifdef dIgnoreLight
#if defined(dIgnoreLight)
#ifdef bumpEnabled
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0 && bumpiness > 0.0) {
material.rgb += fbm(vModelPosition * uBumpFrequency) * uBumpAmplitude * bumpiness;
@@ -16,6 +16,10 @@ export const apply_light_color = `
}
#endif
#if defined(dRenderVariant_color)
material.rgb += material.rgb * emissive;
#endif
gl_FragColor = material;
#else
#ifdef bumpEnabled
@@ -66,6 +70,10 @@ export const apply_light_color = `
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
outgoingLight = clamp(outgoingLight, 0.01, 0.99); // prevents black artifacts on specular highlight with transparent background
#if defined(dRenderVariant_color)
outgoingLight += color.rgb * emissive;
#endif
gl_FragColor = vec4(outgoingLight, color.a);
#endif

View File

@@ -65,6 +65,20 @@ export const assign_color_varying = `
vOverpaint *= uOverpaintStrength;
#endif
#ifdef dEmissive
#if defined(dEmissiveType_instance)
vEmissive = readFromTexture(tEmissive, aInstance, uEmissiveTexDim).a;
#elif defined(dEmissiveType_groupInstance)
vEmissive = readFromTexture(tEmissive, aInstance * float(uGroupCount) + group, uEmissiveTexDim).a;
#elif defined(dEmissiveType_vertexInstance)
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + VertexID, uEmissiveTexDim).a;
#elif defined(dEmissiveType_volumeInstance)
vec3 egridPos = (uEmissiveGridTransform.w * (vModelPosition - uEmissiveGridTransform.xyz)) / uEmissiveGridDim;
vEmissive = texture3dFrom2dLinear(tEmissiveGrid, egridPos, uEmissiveGridDim, uEmissiveTexDim).a;
#endif
vEmissive *= uEmissiveStrength;
#endif
#ifdef dSubstance
#if defined(dSubstanceType_instance)
vSubstance = readFromTexture(tSubstance, aInstance, uSubstanceTexDim);
@@ -81,6 +95,20 @@ export const assign_color_varying = `
vSubstance.rgb = mix(vec3(uMetalness, uRoughness, uBumpiness), vSubstance.rgb, vSubstance.a);
vSubstance *= uSubstanceStrength;
#endif
#elif defined(dRenderVariant_emissive)
#ifdef dEmissive
#if defined(dEmissiveType_instance)
vEmissive = readFromTexture(tEmissive, aInstance, uEmissiveTexDim).a;
#elif defined(dEmissiveType_groupInstance)
vEmissive = readFromTexture(tEmissive, aInstance * float(uGroupCount) + group, uEmissiveTexDim).a;
#elif defined(dEmissiveType_vertexInstance)
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + VertexID, uEmissiveTexDim).a;
#elif defined(dEmissiveType_volumeInstance)
vec3 egridPos = (uEmissiveGridTransform.w * (vModelPosition - uEmissiveGridTransform.xyz)) / uEmissiveGridDim;
vEmissive = texture3dFrom2dLinear(tEmissiveGrid, egridPos, uEmissiveGridDim, uEmissiveTexDim).a;
#endif
vEmissive *= uEmissiveStrength;
#endif
#elif defined(dRenderVariant_pick)
#ifdef requiredDrawBuffers
vObject = vec4(packIntToRGB(float(uObjectId)), 1.0);

View File

@@ -20,13 +20,19 @@ export const assign_material_color = `
material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
float emissive = uEmissive;
#ifdef dEmissive
emissive += vEmissive;
#endif
float metalness = uMetalness;
float roughness = uRoughness;
float bumpiness = uBumpiness;
#ifdef dSubstance
metalness = mix(metalness, vSubstance.r, vSubstance.a);
roughness = mix(roughness, vSubstance.g, vSubstance.a);
bumpiness = mix(bumpiness, vSubstance.b, vSubstance.a);
float sf = clamp(vSubstance.a, 0.0, 0.99); // clamp to avoid artifacts
metalness = mix(metalness, vSubstance.r, sf);
roughness = mix(roughness, vSubstance.g, sf);
bumpiness = mix(bumpiness, vSubstance.b, sf);
#endif
#elif defined(dRenderVariant_depth)
if (fragmentDepth > getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
@@ -81,44 +87,27 @@ export const assign_material_color = `
discard;
material = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0 - fogFactor);
}
#elif defined(dRenderVariant_emissive)
float emissive = uEmissive;
#ifdef dEmissive
emissive += vEmissive;
#endif
vec4 material = vec4(emissive);
#endif
// apply per-group transparency
#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color))
#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color) || defined(dRenderVariant_emissive))
float ta = 1.0 - vTransparency;
if (vTransparency < 0.09) ta = 1.0; // hard cutoff looks better
#if defined(dRenderVariant_pick)
if (ta < uPickingAlphaThreshold)
if (ta * uAlpha < uPickingAlphaThreshold)
discard; // ignore so the element below can be picked
#elif defined(dRenderVariant_emissive)
if (ta < 1.0)
discard; // emissive not supported with transparency
#elif defined(dRenderVariant_color)
material.a *= ta;
#if defined(dRenderVariant_colorBlended)
#if defined(dTransparentBackfaces_off)
if ((uRenderMask == MaskOpaque && material.a < 1.0) ||
(uRenderMask == MaskTransparent && material.a == 1.0) ||
(interior && material.a < 1.0)
) {
discard;
}
#elif defined(dTransparentBackfaces_on)
if ((uRenderMask == MaskOpaque && material.a < 1.0) ||
(uRenderMask == MaskTransparent && material.a == 1.0)
) {
discard;
}
#elif defined(dTransparentBackfaces_opaque)
if (interior) {
material.a = 1.0;
} else if (
(uRenderMask == MaskOpaque && material.a < 1.0) ||
(uRenderMask == MaskTransparent && material.a == 1.0)
) {
discard;
}
#endif
#endif
#endif
#endif
`;

View File

@@ -0,0 +1,17 @@
export const check_transparency = `
#if defined(dRenderVariant_color)
#if defined(dTransparentBackfaces_off)
if (interior && material.a < 1.0) discard;
#elif defined(dTransparentBackfaces_opaque)
if (interior) material.a = 1.0;
#endif
#if !defined(dXrayShaded_on) && !defined(dXrayShaded_inverted)
if ((uRenderMask == MaskOpaque && material.a < 1.0) ||
(uRenderMask == MaskTransparent && material.a == 1.0)
) {
discard;
}
#endif
#endif
`;

View File

@@ -6,6 +6,7 @@ uniform float uBumpiness;
uniform float uBumpFrequency;
uniform float uBumpAmplitude;
#endif
uniform float uEmissive;
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
@@ -23,9 +24,17 @@ uniform float uBumpiness;
varying vec4 vOverpaint;
#endif
#ifdef dEmissive
varying float vEmissive;
#endif
#ifdef dSubstance
varying vec4 vSubstance;
#endif
#elif defined(dRenderVariant_emissive)
#ifdef dEmissive
varying float vEmissive;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100 || !defined(dVaryingGroup)
#ifdef requiredDrawBuffers

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