mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 15:14:22 +08:00
Compare commits
411 Commits
v0.5.0-dev
...
v0.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
472def49f6 | ||
|
|
90549893e3 | ||
|
|
73a8e45202 | ||
|
|
35df55cb4f | ||
|
|
c2028d20a8 | ||
|
|
eaffdc6a98 | ||
|
|
0b1e6100a9 | ||
|
|
2168905c11 | ||
|
|
f955e6a299 | ||
|
|
98f3981e12 | ||
|
|
82f94d20ea | ||
|
|
fbc6d47117 | ||
|
|
6a2e4cf813 | ||
|
|
4aabef7683 | ||
|
|
74ae91ee1b | ||
|
|
8b62766474 | ||
|
|
2995504916 | ||
|
|
63f6848d26 | ||
|
|
988e429693 | ||
|
|
ba1c6ef046 | ||
|
|
7ceff92a4e | ||
|
|
1f968b2836 | ||
|
|
d97d7e3b14 | ||
|
|
5c4c4811e4 | ||
|
|
f2a6e63a20 | ||
|
|
1aace4a26f | ||
|
|
b1c140d23e | ||
|
|
ee16212c31 | ||
|
|
f6d232b1c5 | ||
|
|
c8a002933e | ||
|
|
4757ca9913 | ||
|
|
5264c75e37 | ||
|
|
032bf44863 | ||
|
|
2f4f5e43f3 | ||
|
|
7b1edcadf6 | ||
|
|
f0f74d182d | ||
|
|
59142adbbc | ||
|
|
2fda8c5db1 | ||
|
|
7b5efa3e42 | ||
|
|
d784d202bd | ||
|
|
8833474a43 | ||
|
|
57cbb2f8b6 | ||
|
|
b6112a914f | ||
|
|
2008f8538c | ||
|
|
09fba43a1c | ||
|
|
b76c3613f9 | ||
|
|
cf4ddcb587 | ||
|
|
696106f48b | ||
|
|
fb286cd9cf | ||
|
|
5121bd700e | ||
|
|
6173520ad0 | ||
|
|
a7189232dd | ||
|
|
4a96b45b04 | ||
|
|
20ac549dd6 | ||
|
|
38be00c0b7 | ||
|
|
0a9bdc8cf6 | ||
|
|
0b4318280a | ||
|
|
b1da60e1c0 | ||
|
|
2cc5987f9e | ||
|
|
745415a1d8 | ||
|
|
c80658f368 | ||
|
|
033675a417 | ||
|
|
3dd57e9dc8 | ||
|
|
1e3daa6c98 | ||
|
|
8855f51cfc | ||
|
|
50d8debb2b | ||
|
|
e682eb78b0 | ||
|
|
bd2bbf3e2d | ||
|
|
f38f040aea | ||
|
|
f912b2d802 | ||
|
|
0ad03e6a0b | ||
|
|
160d8c529e | ||
|
|
28edfd810c | ||
|
|
7b931cfb66 | ||
|
|
5575c61577 | ||
|
|
8f93dce105 | ||
|
|
0eb3d7226a | ||
|
|
a9533b666c | ||
|
|
6d67b4db56 | ||
|
|
1b5eff6454 | ||
|
|
83fb28cc9d | ||
|
|
c39ffc0b0e | ||
|
|
23892cfbdd | ||
|
|
3bdabc444d | ||
|
|
c233be4467 | ||
|
|
8468f33803 | ||
|
|
0e77369fdb | ||
|
|
9fcc8e7977 | ||
|
|
538371ced8 | ||
|
|
380887bd22 | ||
|
|
17b4b1cb86 | ||
|
|
3e7c358c07 | ||
|
|
05b592a173 | ||
|
|
e1d0515fae | ||
|
|
d4d3b9645e | ||
|
|
d12d99dcfa | ||
|
|
174324d21c | ||
|
|
26156a5982 | ||
|
|
af5ddf6950 | ||
|
|
2ef35e5fb9 | ||
|
|
513bfeaae7 | ||
|
|
7311e6f484 | ||
|
|
90ecedcae8 | ||
|
|
352a20ac48 | ||
|
|
b1d8c5f6ea | ||
|
|
6497be9e52 | ||
|
|
40a8cee8e5 | ||
|
|
112cfcac90 | ||
|
|
8016fcbd54 | ||
|
|
6de97f1d03 | ||
|
|
204075bbe0 | ||
|
|
eb448bce37 | ||
|
|
3d09b5cb67 | ||
|
|
35d06040f7 | ||
|
|
0868e81944 | ||
|
|
7efbeb7d0f | ||
|
|
815f61b550 | ||
|
|
c3a90ab499 | ||
|
|
321126afa2 | ||
|
|
9ad4a9c3c9 | ||
|
|
b12f7c7ce4 | ||
|
|
6d7d3c0794 | ||
|
|
92b988a8d5 | ||
|
|
18952ee2bd | ||
|
|
bff07888f9 | ||
|
|
8e6b0b220a | ||
|
|
74acb0d078 | ||
|
|
d4727eea02 | ||
|
|
76d14eba0c | ||
|
|
90d05d9260 | ||
|
|
23b24bbb6c | ||
|
|
91bc6f07c5 | ||
|
|
9b2181667d | ||
|
|
40347e3e46 | ||
|
|
ed277f6e16 | ||
|
|
8f2085d8b4 | ||
|
|
b5a48ad201 | ||
|
|
14b900791f | ||
|
|
de80799e68 | ||
|
|
53eca387fc | ||
|
|
fc3005f271 | ||
|
|
c95fdff00c | ||
|
|
3cd9042c72 | ||
|
|
4d3914426e | ||
|
|
af1fb7041e | ||
|
|
e2eb1bf223 | ||
|
|
5a64a6f1a3 | ||
|
|
88aa9303d7 | ||
|
|
5c77eec184 | ||
|
|
a931ed7c01 | ||
|
|
94cd2b618c | ||
|
|
9c97fc258d | ||
|
|
0ac1cfe555 | ||
|
|
3942f1bc33 | ||
|
|
a66e38a901 | ||
|
|
f65f4f4aeb | ||
|
|
f28b13bf87 | ||
|
|
8f211a0785 | ||
|
|
ba1dfb2851 | ||
|
|
964d56752e | ||
|
|
cb6a66eba5 | ||
|
|
94adf7259d | ||
|
|
3c831b549a | ||
|
|
7ccf36a0fa | ||
|
|
b0ee640c12 | ||
|
|
cd6d41a035 | ||
|
|
9c8f1f3e11 | ||
|
|
7c50a5a456 | ||
|
|
f3b6703fd4 | ||
|
|
60f2716b5a | ||
|
|
d7007ef99d | ||
|
|
4aca41cdbb | ||
|
|
7e31fac99a | ||
|
|
3bcf1cb6b5 | ||
|
|
a29cc74304 | ||
|
|
95c43d0864 | ||
|
|
369b958335 | ||
|
|
9a17da8f65 | ||
|
|
0c4ba20d6e | ||
|
|
22936ff6c9 | ||
|
|
3369ad0bdc | ||
|
|
20853ef60b | ||
|
|
da0c66bd8e | ||
|
|
44664917ff | ||
|
|
ce26e002c7 | ||
|
|
345b9f546c | ||
|
|
f4ed5e301d | ||
|
|
4818f851b3 | ||
|
|
6f2c47c0ca | ||
|
|
5e6bc0f0df | ||
|
|
083daa0b76 | ||
|
|
139a10ba8c | ||
|
|
0b512487f5 | ||
|
|
a85adfdf1f | ||
|
|
0171b785af | ||
|
|
4c1c484af9 | ||
|
|
db7585b6b6 | ||
|
|
d263f6d929 | ||
|
|
034c28e487 | ||
|
|
9a88f57ce6 | ||
|
|
28415646a2 | ||
|
|
b03295ef81 | ||
|
|
7d3e849ff3 | ||
|
|
926f20a6a4 | ||
|
|
c12d577ce6 | ||
|
|
aa8d3a9841 | ||
|
|
f11e03eb85 | ||
|
|
ab996b5189 | ||
|
|
4c91e730a2 | ||
|
|
5ca5f44c61 | ||
|
|
f8a89b1c0d | ||
|
|
99dacef747 | ||
|
|
24fc37105e | ||
|
|
cbd493d834 | ||
|
|
53dbd1aedf | ||
|
|
6ab1af54aa | ||
|
|
615d9758bb | ||
|
|
341f9f8c91 | ||
|
|
ba7d4a5215 | ||
|
|
428187ad81 | ||
|
|
fe96172fcd | ||
|
|
8b13be698c | ||
|
|
5edc924e4f | ||
|
|
78c50a4339 | ||
|
|
607284585c | ||
|
|
f7af352f03 | ||
|
|
008ec2c88c | ||
|
|
c91074ad91 | ||
|
|
51fbb619e5 | ||
|
|
062c4b335b | ||
|
|
d4107e273d | ||
|
|
4dad67fc2e | ||
|
|
64b0713742 | ||
|
|
22c45e788e | ||
|
|
75de544669 | ||
|
|
768ec10809 | ||
|
|
4d5d9be399 | ||
|
|
7f4afc111e | ||
|
|
0365f883dd | ||
|
|
6f98549f31 | ||
|
|
77920818d9 | ||
|
|
0682d54ee2 | ||
|
|
fbcc3b2fa2 | ||
|
|
c3f47b2ecb | ||
|
|
8b9f59ac5a | ||
|
|
362dcabe5c | ||
|
|
6e205661bd | ||
|
|
c1a8627702 | ||
|
|
2c5253943c | ||
|
|
03668216fa | ||
|
|
37ef234803 | ||
|
|
d25aa97fca | ||
|
|
17dc973305 | ||
|
|
eed4e4a134 | ||
|
|
5fd28c6cad | ||
|
|
0f69ea197d | ||
|
|
e1a214e1a8 | ||
|
|
e7e14750cb | ||
|
|
cb83013967 | ||
|
|
0a84e1bb7b | ||
|
|
aee7f4988a | ||
|
|
e762c402fa | ||
|
|
4375cae70d | ||
|
|
90268a9041 | ||
|
|
a3ebc4df45 | ||
|
|
b2743c76e0 | ||
|
|
969a70d571 | ||
|
|
e6a538dbd7 | ||
|
|
9cffce55c9 | ||
|
|
7bc91d7e99 | ||
|
|
943f5feb59 | ||
|
|
dc72dbbc97 | ||
|
|
1513a1e2d2 | ||
|
|
82989cae57 | ||
|
|
74f8a5053e | ||
|
|
79f7ea209a | ||
|
|
ba65e35c51 | ||
|
|
5bc4e3ce3b | ||
|
|
1c7ac60c11 | ||
|
|
00cd54b69c | ||
|
|
90e3a3f0c7 | ||
|
|
5eef3fc42a | ||
|
|
83a8474731 | ||
|
|
a5e13c5152 | ||
|
|
b19f53d380 | ||
|
|
abadef1d2e | ||
|
|
a3d976c5b8 | ||
|
|
414b20f06d | ||
|
|
d03a442b6c | ||
|
|
e5acce03e5 | ||
|
|
fe714d4515 | ||
|
|
7959344bca | ||
|
|
b9582f171d | ||
|
|
410123d933 | ||
|
|
ae484443d8 | ||
|
|
9d97e56f03 | ||
|
|
01269ec1ef | ||
|
|
a204264bcc | ||
|
|
eb0a048926 | ||
|
|
ba1509b37a | ||
|
|
ad379e7a32 | ||
|
|
9d45beea3b | ||
|
|
b2d134aeb4 | ||
|
|
d500b8ea19 | ||
|
|
8b3df4b373 | ||
|
|
f01fc7c8ba | ||
|
|
0e6da0bffa | ||
|
|
eab8b1c2bf | ||
|
|
8b42a0fede | ||
|
|
4bbe078230 | ||
|
|
aabb931d27 | ||
|
|
b1c1b21975 | ||
|
|
7b5dc3ef96 | ||
|
|
1f831f90e0 | ||
|
|
ef2c1b51e9 | ||
|
|
ace73c041b | ||
|
|
3c70fe5303 | ||
|
|
137e23c025 | ||
|
|
969a19d515 | ||
|
|
7536abe96c | ||
|
|
8e20e163f9 | ||
|
|
adbe8e1f67 | ||
|
|
5db134f34f | ||
|
|
80cf7c1dd2 | ||
|
|
9e5cc184ed | ||
|
|
a5185b456c | ||
|
|
0615198ad2 | ||
|
|
6cd422f5b3 | ||
|
|
4416178d6f | ||
|
|
b8cef99dc1 | ||
|
|
4dcb68af33 | ||
|
|
b4c70ab14a | ||
|
|
8436e17af9 | ||
|
|
bda04cbdc7 | ||
|
|
7287947a55 | ||
|
|
fb5eb1e19c | ||
|
|
d735dcdc3e | ||
|
|
83d8a61400 | ||
|
|
b72f57c040 | ||
|
|
8a7ef1c704 | ||
|
|
8731bc13d1 | ||
|
|
d103cda0c8 | ||
|
|
ca4e864e46 | ||
|
|
0e25950d51 | ||
|
|
2c29a90b5e | ||
|
|
2900836208 | ||
|
|
928a1df525 | ||
|
|
16e0dff551 | ||
|
|
7e443d5c9b | ||
|
|
172ae17966 | ||
|
|
8cbf7b5d0a | ||
|
|
2c40b85880 | ||
|
|
ad388f23ae | ||
|
|
27b559a2d8 | ||
|
|
c6d19e14c5 | ||
|
|
b807dca2d8 | ||
|
|
2cae6e3f59 | ||
|
|
69c73f3dcd | ||
|
|
4456ab2cd5 | ||
|
|
bdda18de23 | ||
|
|
1b2c2f3d41 | ||
|
|
fd19d29ef6 | ||
|
|
1f0c0fd756 | ||
|
|
83698cc52d | ||
|
|
70b94deb20 | ||
|
|
2da3df6e4d | ||
|
|
509e633a69 | ||
|
|
17fac2b82a | ||
|
|
c543c4e10a | ||
|
|
10073800dc | ||
|
|
04c38250b4 | ||
|
|
5ccb329af1 | ||
|
|
3cecb53bc5 | ||
|
|
0daf431d68 | ||
|
|
3e0c4242ad | ||
|
|
17b775c377 | ||
|
|
4525b98288 | ||
|
|
755699d479 | ||
|
|
a84309b800 | ||
|
|
16863535b8 | ||
|
|
dd9773d72e | ||
|
|
ec45f6c0ee | ||
|
|
59235630bb | ||
|
|
11ed0ca89b | ||
|
|
188ea6e8e2 | ||
|
|
293b464d9f | ||
|
|
0e91aa521e | ||
|
|
5272593cc3 | ||
|
|
c5f336b0e4 | ||
|
|
b1a6fa3ffc | ||
|
|
fe47134934 | ||
|
|
131cc606f0 | ||
|
|
3681f01fad | ||
|
|
e966c112ab | ||
|
|
d7b232b00b | ||
|
|
7986509ad3 | ||
|
|
73dcf970f3 | ||
|
|
9377aa2d05 | ||
|
|
686fa5a5ed | ||
|
|
c946ae6eab | ||
|
|
9b2f1d9415 | ||
|
|
f6c28aa8e2 | ||
|
|
64c72aa6f5 | ||
|
|
0a22917773 | ||
|
|
3c4888e52b | ||
|
|
2c327cfdf6 | ||
|
|
307f2efc97 | ||
|
|
64c51f0d94 | ||
|
|
7a0b4c4d23 | ||
|
|
f45edbc4f0 | ||
|
|
65d3355b18 |
@@ -35,7 +35,7 @@
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
|
||||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@@ -9,6 +9,20 @@
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build-tsc",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -31,6 +31,7 @@ The core of Mol* currently consists of these modules (see under `src/`):
|
||||
- `mol-state` State representation tree with state saving and automatic updates.
|
||||
- `mol-app` Components for builduing UIs.
|
||||
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
|
||||
- `mol-plugin-state` State transformations, builders, and managers.
|
||||
- `mol-plugin-ui` React based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated to 3rd party solutions.
|
||||
- `mol-util` Useful things that do not fit elsewhere.
|
||||
|
||||
@@ -61,6 +62,8 @@ This project builds on experience from previous solutions:
|
||||
### Build automatically on file save:
|
||||
npm run watch
|
||||
|
||||
If working on just the viewer, ``npm run watch-viewer`` will provide shorter compile times.
|
||||
|
||||
### Build with debug mode enabled:
|
||||
DEBUG=molstar npm run watch
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
schema: https://data-beta.rcsb.org/graphql
|
||||
documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts'
|
||||
generates:
|
||||
'./src/mol-model-props/rcsb/graphql/types.d.ts':
|
||||
'./src/mol-model-props/rcsb/graphql/types.ts':
|
||||
plugins:
|
||||
- add: '/* eslint-disable */'
|
||||
- time
|
||||
|
||||
@@ -6,133 +6,64 @@ Model Server is a tool for preprocessing and querying macromolecular structure d
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
Getting the code (use node 8+):
|
||||
Requires nodejs 8+.
|
||||
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Customize configuration at ``src/server/model/config.ts`` to point to your data and which custom properties to include (see the [Custom Properties](#custom-properties) section). Alternatively, the config can be edited in the compiled version in ``build/node_modules/servers/model/config.js``.
|
||||
|
||||
Afterwards, build the project:
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
(or run watch mode for automatic rebuilds: ``npm run watch``)
|
||||
and run the server by
|
||||
|
||||
Running the server locally for testing:
|
||||
```
|
||||
npm run model-server
|
||||
```
|
||||
or
|
||||
```
|
||||
node build/node_modules/servers/model/server
|
||||
node lib/servers/model/server/server
|
||||
```
|
||||
|
||||
In production it is a good idea to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
## 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``
|
||||
|
||||
|
||||
## Memory issues
|
||||
### 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
|
||||
============
|
||||
## Preprocessor
|
||||
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. See the [Custom Properties](#custom-properties) section for providing custom properties.
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
|
||||
|
||||
## Usage
|
||||
|
||||
The app works in two modes: single files and folders.
|
||||
## Local Mode
|
||||
|
||||
Single files:
|
||||
|
||||
```
|
||||
node build\node_modules\servers\model\preprocess -i input.cif [-oc output.cif] [-ob output.bcif] [--cfg config.json]
|
||||
```
|
||||
|
||||
Folder:
|
||||
```
|
||||
node build\node_modules\servers\model\preprocess -fin input_folder [-foc output_cif_folder] [-fob output_bcif_folder] [--cfg config.json]
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
The config speficies the maximum number of processes to use (in case of folder processing) and defines sources and parameters for custom properties.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"numProcesses": 4,
|
||||
"customProperties": {
|
||||
"sources": [
|
||||
"./properties/pdbe"
|
||||
],
|
||||
"params": {
|
||||
"PDBe": {
|
||||
"UseFileSource": false,
|
||||
"API": {
|
||||
"residuewise_outlier_summary": "https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry",
|
||||
"preferred_assembly": "https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary",
|
||||
"struct_ref_domain": "https://www.ebi.ac.uk/pdbe/api/mappings/sequence_domains"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
The server can be run in local/file based mode using ``node lib/servers/model/query`` (``model-server-query`` binary from the NPM package).
|
||||
|
||||
Custom Properties
|
||||
=================
|
||||
|
||||
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``.
|
||||
This feature is still in development.
|
||||
|
||||
Local Mode
|
||||
==========
|
||||
|
||||
The server can be run in local/file based mode:
|
||||
|
||||
```
|
||||
node build/node_modules/servers/model/server jobs.json
|
||||
```
|
||||
|
||||
where ``jobs.json`` is an array of
|
||||
|
||||
```ts
|
||||
type LocalInput = {
|
||||
input: string,
|
||||
output: string,
|
||||
query: QueryName,
|
||||
modelNums?: number[],
|
||||
params?: any,
|
||||
binary?: boolean
|
||||
}[]
|
||||
```
|
||||
|
||||
For example
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"input": "c:/test/quick/1tqn.cif",
|
||||
"output": "c:/test/quick/localapi/1tqn_full.cif",
|
||||
"query": "full"
|
||||
},
|
||||
{
|
||||
"input": "c:/test/quick/1tqn.cif",
|
||||
"output": "c:/test/quick/localapi/1tqn_full.bcif",
|
||||
"query": "full",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"input": "c:/test/quick/1cbs_updated.cif",
|
||||
"output": "c:/test/quick/localapi/1cbs_ligint.cif",
|
||||
"query": "residueInteraction",
|
||||
"params": {
|
||||
"atom_site": { "label_comp_id": "REA" }
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
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``.
|
||||
@@ -7,107 +7,64 @@ It uses the text based CIF and BinaryCIF formats to deliver the data to the clie
|
||||
|
||||
For quick info about the benefits of using the server, check out the [examples](examples.md).
|
||||
|
||||
Installing the Server
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
- Install [Node.js](https://nodejs.org/en/) (tested on Node 6.* and 7.*; x64 version is strongly preferred).
|
||||
- Get the code.
|
||||
- Prepare the data.
|
||||
- Run the server.
|
||||
Requires nodejs 8+.
|
||||
|
||||
Preparing the Data
|
||||
------------------
|
||||
## 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/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.
|
||||
To achieve this, use the ``pack`` application (``node lib/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
|
||||
- To prepare data from x-ray based methods, use:
|
||||
## Local Mode
|
||||
|
||||
```
|
||||
node pack -xray main.ccp4 diff.ccp4 out.mdb
|
||||
```
|
||||
|
||||
- For EM data, use:
|
||||
|
||||
```
|
||||
node pack -em em.map out.mdb
|
||||
```
|
||||
|
||||
Running the Server
|
||||
------------------
|
||||
|
||||
- Install production dependencies:
|
||||
|
||||
```
|
||||
npm install --only=production
|
||||
```
|
||||
|
||||
- Update ``server-config.js`` to link to your data and optionally tweak the other parameters.
|
||||
|
||||
- Run it:
|
||||
|
||||
```
|
||||
node server
|
||||
```
|
||||
|
||||
In production it is a good idea to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
### Local Mode
|
||||
|
||||
The program ``local`` in the build folder can be used to query the data without running a http server.
|
||||
|
||||
- ``node local`` prints the program usage.
|
||||
- ``node local jobs.json`` takes a list of jobs to execute in JSON format. A job entry is defined by this interface:
|
||||
|
||||
```TypeScript
|
||||
interface JobEntry {
|
||||
source: {
|
||||
filename: string,
|
||||
name: string,
|
||||
id: string
|
||||
},
|
||||
query: {
|
||||
kind: 'box' | 'cell',
|
||||
space?: 'fractional' | 'cartesian',
|
||||
bottomLeft?: number[],
|
||||
topRight?: number[],
|
||||
}
|
||||
params: {
|
||||
/** Determines the detail level as specified in server-config */
|
||||
detail?: number,
|
||||
/**
|
||||
* Determines the sampling level:
|
||||
* 1: Original data
|
||||
* 2: Downsampled by factor 1/2
|
||||
* ...
|
||||
* N: downsampled 1/2^(N-1)
|
||||
*/
|
||||
forcedSamplingLevel?: number,
|
||||
asBinary: boolean,
|
||||
},
|
||||
outputFolder: string
|
||||
}
|
||||
```
|
||||
|
||||
Example ``jobs.json`` file content:
|
||||
|
||||
```TypeScript
|
||||
[{
|
||||
source: {
|
||||
filename: `g:/test/mdb/emd-8116.mdb`,
|
||||
name: 'em',
|
||||
id: '8116',
|
||||
},
|
||||
query: {
|
||||
kind: 'cell'
|
||||
},
|
||||
params: {
|
||||
detail: 4,
|
||||
asBinary: true
|
||||
},
|
||||
outputFolder: 'g:/test/local-test'
|
||||
}]
|
||||
```
|
||||
The program ``lib/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
|
||||
|
||||
@@ -122,8 +79,8 @@ The source code is split into 2 mains parts: ``pack`` and ``server``:
|
||||
Consuming the Data
|
||||
==================
|
||||
|
||||
The data can be consumed in any (modern) browser using the [CIFTools.js library](https://github.com/dsehnal/CIFTools.js) (or any other piece of code that can read text or binary CIF).
|
||||
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, please see the implementation in [LiteMol](https://github.com/dsehnal/LiteMol) ([CIF.ts + Data.ts](https://github.com/dsehnal/LiteMol/tree/master/src/lib/Core/Formats/Density), [UI](https://github.com/dsehnal/LiteMol/tree/master/src/Viewer/Extensions/DensityStreaming)) or in Mol*.
|
||||
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
|
||||
5797
package-lock.json
generated
5797
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
71
package.json
71
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.5.0-dev.2",
|
||||
"version": "0.6.0-dev.9",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -14,17 +14,19 @@
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"test": "npm run lint && jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"build-tsc": "tsc",
|
||||
"build-tsc": "tsc --incremental",
|
||||
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/",
|
||||
"build-webpack": "webpack --mode production",
|
||||
"watch": "concurrently --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-tsc": "tsc -watch",
|
||||
"watch": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --display minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
|
||||
"serve": "http-server -p 1338",
|
||||
"model-server": "node lib/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
|
||||
"volume-server": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"volume-server-test": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/plugin-state/index.js",
|
||||
"preversion": "npm run test",
|
||||
"postversion": "git push && git push --tags",
|
||||
@@ -33,6 +35,14 @@
|
||||
"files": [
|
||||
"lib/"
|
||||
],
|
||||
"bin": {
|
||||
"model-server": "lib/servers/model/server.js",
|
||||
"model-server-query": "lib/servers/model/local.js",
|
||||
"model-server-preprocess": "lib/servers/model/preprocess.js",
|
||||
"volume-server": "lib/servers/volume/server.js",
|
||||
"volume-server-query": "lib/servers/volume/query.js",
|
||||
"volume-server-pack": "lib/servers/volume/pack.js"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignoreRoot": [
|
||||
"./node_modules",
|
||||
@@ -64,17 +74,16 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.12.2",
|
||||
"@graphql-codegen/cli": "^1.12.2",
|
||||
"@graphql-codegen/time": "^1.12.2",
|
||||
"@graphql-codegen/typescript": "^1.12.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.12.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.12.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.12.2",
|
||||
"@graphql-codegen/add": "^1.13.1",
|
||||
"@graphql-codegen/cli": "^1.13.1",
|
||||
"@graphql-codegen/time": "^1.13.1",
|
||||
"@graphql-codegen/typescript": "^1.13.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.1",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.1",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.19.2",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "^2.19.2",
|
||||
"@typescript-eslint/parser": "^2.19.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
@@ -82,10 +91,10 @@
|
||||
"css-loader": "^3.4.2",
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^5.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.1.0",
|
||||
"jest": "^25.2.4",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
@@ -93,22 +102,22 @@
|
||||
"raw-loader": "^4.0.0",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.131.0",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-jest": "^25.2.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-cli": "^3.3.10"
|
||||
"ts-jest": "^25.3.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/jest": "^25.1.2",
|
||||
"@types/node": "^13.7.0",
|
||||
"@types/node-fetch": "^2.5.4",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^13.9.8",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/react": "^16.9.29",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
@@ -117,12 +126,14 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.6.0",
|
||||
"immer": "^6.0.2",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"rxjs": "^6.5.4",
|
||||
"swagger-ui-dist": "^3.25.0",
|
||||
"tslib": "^1.11.1",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder } from '../../mol-state';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { BuiltInColorThemeName } from '../../mol-theme/color';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
|
||||
export namespace StateHelper {
|
||||
@@ -58,36 +58,41 @@ export namespace StateHelper {
|
||||
|
||||
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation,
|
||||
{ axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() },
|
||||
{ transform: { name: 'components', params: { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() } } },
|
||||
{ tags: 'transform' });
|
||||
}
|
||||
|
||||
export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformationByMatrix, { matrix }, { tags: 'transform' });
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation, {
|
||||
transform: { name: 'matrix', params: matrix }
|
||||
}, { tags: 'transform' });
|
||||
}
|
||||
|
||||
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
|
||||
return b.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: id || 'deposited' }, { tags: 'asm' })
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: id || 'deposited' }
|
||||
}
|
||||
}
|
||||
return b.apply(StateTransforms.Model.StructureFromModel, props, { tags: 'asm' })
|
||||
}
|
||||
|
||||
export function visual(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'), { tags: 'seq-visual' });
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'cartoon' }), { tags: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'), { tags: 'het-visual' });
|
||||
// visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }), { tags: 'water-visual' });
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick' }), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, coloring?: BuiltInColorThemeName) {
|
||||
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, color?: ColorTheme.BuiltIn) {
|
||||
visualRoot
|
||||
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', void 0, coloring), { tags: 'het-visual' });
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick', color }), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { StateBuilder, StateTransform } from '../../mol-state';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
|
||||
@@ -21,6 +20,7 @@ import { CustomToastMessage } from './controls';
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
@@ -47,8 +47,8 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.propertyProvider.descriptor.name, StripedResidues.colorThemeProvider!);
|
||||
this.plugin.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
|
||||
@@ -61,25 +61,31 @@ class BasicWrapper {
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon'), { ref: 'seq-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'cartoon' }), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick', { alpha: 0.51 }), { ref: 'water-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick', typeParams: { alpha: 0.51 } }), { ref: 'water-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill'), { ref: 'ihm-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill' }), { ref: 'ihm-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ class BasicWrapper {
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -97,22 +103,29 @@ class BasicWrapper {
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
const renderer = this.plugin.canvas3d!.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
@@ -120,8 +133,8 @@ class BasicWrapper {
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
animate = {
|
||||
@@ -137,68 +150,68 @@ class BasicWrapper {
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
const visuals = state.selectQ(q => q.ofTransformer(StateTransforms.Representation.StructureRepresentation3D));
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
|
||||
const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
for (const v of visuals) {
|
||||
tree.to(v).update(old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const seq_id = 7;
|
||||
const data = (this.plugin.state.dataState.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
|
||||
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
this.plugin.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
},
|
||||
clearHighlight: () => {
|
||||
this.plugin.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
const tree = buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
},
|
||||
dynamicSuperposition: async () => {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.plugin.state.dataState, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state: this.plugin.state.data, ref: StateTransform.RootRef });
|
||||
await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
|
||||
},
|
||||
toggleValidationTooltip: async () => {
|
||||
const state = this.plugin.state.behaviorState;
|
||||
const state = this.plugin.state.behaviors;
|
||||
const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip }));
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
},
|
||||
showToasts: () => {
|
||||
PluginCommands.Toast.Show.dispatch(this.plugin, {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 1',
|
||||
message: 'This is an example text, timeout 3s',
|
||||
key: 'toast-1',
|
||||
timeoutMs: 3000
|
||||
});
|
||||
PluginCommands.Toast.Show.dispatch(this.plugin, {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 2',
|
||||
message: CustomToastMessage,
|
||||
key: 'toast-2'
|
||||
});
|
||||
},
|
||||
hideToasts: () => {
|
||||
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-2' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { StateHelper } from './helpers';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateSelection, StateBuilder } from '../../mol-state';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelection, QueryContext } from '../../mol-model/structure';
|
||||
@@ -32,7 +32,7 @@ export type SuperpositionTestInput = {
|
||||
// }
|
||||
|
||||
export function buildStaticSuperposition(ctx: PluginContext, src: SuperpositionTestInput) {
|
||||
const b = ctx.state.dataState.build().toRoot();
|
||||
const b = ctx.state.data.build().toRoot();
|
||||
for (const s of src) {
|
||||
StateHelper.visual(ctx,
|
||||
StateHelper.transform(
|
||||
@@ -63,7 +63,7 @@ export const StaticSuperpositionTestData: SuperpositionTestInput = [
|
||||
];
|
||||
|
||||
export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[], comp_id: string) {
|
||||
const state = ctx.state.dataState;
|
||||
const state = ctx.state.data;
|
||||
|
||||
const structures = state.build().toRoot();
|
||||
for (const s of src) {
|
||||
@@ -71,7 +71,7 @@ export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[]
|
||||
StateHelper.getModel(StateHelper.download(structures, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`), 'cif'));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(ctx, { state, tree: structures });
|
||||
await PluginCommands.State.Update(ctx, { state, tree: structures });
|
||||
|
||||
const pivot = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
@@ -99,7 +99,7 @@ export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[]
|
||||
pivot, rest);
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(ctx, { state, tree: visuals });
|
||||
await PluginCommands.State.Update(ctx, { state, tree: visuals });
|
||||
}
|
||||
|
||||
function siteVisual(ctx: PluginContext, b: StateBuilder.To<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) {
|
||||
|
||||
@@ -144,9 +144,9 @@ async function createBonds() {
|
||||
const comp_id: string[] = []
|
||||
const atom_id_1: string[] = []
|
||||
const atom_id_2: string[] = []
|
||||
const value_order: string[] = []
|
||||
const pdbx_aromatic_flag: string[] = []
|
||||
const pdbx_stereo_config: string[] = []
|
||||
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
|
||||
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = []
|
||||
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = []
|
||||
const molstar_protonation_variant: string[] = []
|
||||
|
||||
function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) {
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../../mol-plugin-state/transforms';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects';
|
||||
import { StateBuilder } from '../../../mol-state';
|
||||
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
|
||||
import { createStructureRepresentationParams } from '../../../mol-plugin-state/helpers/structure-representation-params';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
@@ -94,7 +94,7 @@ class LightingDemo {
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = getPreset(preset)
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
@@ -120,18 +120,24 @@ class LightingDemo {
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill', color: 'illustrative' }), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
@@ -139,7 +145,7 @@ class LightingDemo {
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -149,17 +155,23 @@ class LightingDemo {
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<title>Mol* ModelServer Query Builder</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import * as Rx from 'rxjs'
|
||||
|
||||
import { QueryDefinition, QueryList } from '../../servers/model/server/api'
|
||||
|
||||
import './index.html'
|
||||
|
||||
interface State {
|
||||
query: Rx.BehaviorSubject<QueryDefinition>,
|
||||
id: Rx.BehaviorSubject<string>,
|
||||
params: Rx.BehaviorSubject<any>,
|
||||
isBinary: Rx.BehaviorSubject<boolean>,
|
||||
models: Rx.BehaviorSubject<number[]>,
|
||||
url: Rx.Subject<string>
|
||||
}
|
||||
|
||||
class Root extends React.Component<{ state: State }, { }> {
|
||||
render() {
|
||||
return <div>
|
||||
<div>
|
||||
Query: <QuerySelect state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
ID: <input type='text' onChange={t => this.props.state.id.next(t.currentTarget.value)} />
|
||||
</div>
|
||||
<div>
|
||||
Params:<br/>
|
||||
<QueryParams state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
Model numbers (empty for all): <ModelNums state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
<input type='checkbox' onChange={t => this.props.state.isBinary.next(!!t.currentTarget.checked)} /> Binary
|
||||
</div>
|
||||
<div>
|
||||
Query string:
|
||||
<QueryUrl state={this.props.state} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
class QuerySelect extends React.Component<{ state: State }> {
|
||||
render() {
|
||||
return <select onChange={s => this.props.state.query.next(QueryList[+s.currentTarget.value].definition)}>
|
||||
{ QueryList.map((q, i) => <option value={i} key={i} selected={i === 1}>{q.definition.niceName}</option>) }
|
||||
</select>
|
||||
}
|
||||
}
|
||||
|
||||
class QueryParams extends React.Component<{ state: State }, { prms: string }> {
|
||||
state = { prms: '' };
|
||||
|
||||
parseParams(str: string) {
|
||||
this.setState({ prms: str });
|
||||
try {
|
||||
const params = JSON.parse(str);
|
||||
this.props.state.params.next(params);
|
||||
} catch {
|
||||
this.props.state.params.next({});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.state.query.subscribe(q => this.setState({ prms: formatParams(q) }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return <textarea style={{height: '300px'}} value={this.state.prms} cols={80} onChange={t => this.parseParams(t.currentTarget.value)} />;
|
||||
}
|
||||
}
|
||||
|
||||
class QueryUrl extends React.Component<{ state: State }, { queryString: string }> {
|
||||
state = { queryString: '' };
|
||||
|
||||
componentDidMount() {
|
||||
this.props.state.url.subscribe(url => this.setState({ queryString: url }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input type='text' value={this.state.queryString} style={{ width: '800px' }} />
|
||||
}
|
||||
}
|
||||
|
||||
class ModelNums extends React.Component<{ state: State }> {
|
||||
render() {
|
||||
return <input type='text' defaultValue='1' style={{ width: '300px' }} onChange={t =>
|
||||
this.props.state.models.next(t.currentTarget.value.split(',')
|
||||
.map(v => v.trim())
|
||||
.filter(v => !!v)
|
||||
.map(v => +v)
|
||||
)} />
|
||||
}
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
query: new Rx.BehaviorSubject(QueryList[1].definition),
|
||||
id: new Rx.BehaviorSubject('1cbs'),
|
||||
params: new Rx.BehaviorSubject({ }),
|
||||
isBinary: new Rx.BehaviorSubject<boolean>(false),
|
||||
models: new Rx.BehaviorSubject<number[]>([]),
|
||||
url: new Rx.Subject()
|
||||
}
|
||||
|
||||
function formatParams(def: QueryDefinition) {
|
||||
const prms = Object.create(null);
|
||||
for (const p of def.jsonParams) {
|
||||
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
|
||||
}
|
||||
return JSON.stringify(prms, void 0, 2);
|
||||
}
|
||||
|
||||
function formatUrl() {
|
||||
const json = JSON.stringify({
|
||||
name: state.query.value.name,
|
||||
id: state.id.value,
|
||||
modelNums: state.models.value.length ? state.models.value : void 0,
|
||||
binary: state.isBinary.value,
|
||||
params: state.params.value
|
||||
});
|
||||
state.url.next(encodeURIComponent(json));
|
||||
}
|
||||
|
||||
Rx.merge(state.query, state.id, state.params, state.isBinary, state.models).subscribe(s => formatUrl());
|
||||
|
||||
ReactDOM.render(<Root state={state} />, document.getElementById('app'));
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as _ from '../../mol-plugin/state/transforms'
|
||||
import * as _ from '../../mol-plugin-state/transforms'
|
||||
import { StateTransformer, StateObject } from '../../mol-state';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import * as fs from 'fs';
|
||||
|
||||
@@ -19,7 +19,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'conditioned': return getParams(param.conditionParams, offset);
|
||||
case 'multi-select': return `Array of ${oToS(param.options)}`;
|
||||
case 'color': return 'Color as 0xrrggbb';
|
||||
case 'color-list': return `One of ${oToS(param.options)}`;
|
||||
case 'color-list': return `A list of colors as 0xrrggbb`;
|
||||
case 'vec3': return `3D vector [x, y, z]`;
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
@@ -39,7 +39,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function oToS(options: readonly (readonly [string, string])[]) {
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string | undefined])[]) {
|
||||
return options.map(o => `'${o[0]}'`).join(', ');
|
||||
}
|
||||
|
||||
|
||||
@@ -123,18 +123,6 @@ export function printSequence(model: Model) {
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function printModRes(model: Model) {
|
||||
console.log('\nModified Residues\n=============');
|
||||
const map = model.properties.modifiedResidues.parentId;
|
||||
const { label_comp_id, _rowCount } = model.atomicHierarchy.residues;
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const comp_id = label_comp_id.value(i);
|
||||
if (!map.has(comp_id)) continue;
|
||||
console.log(`[${i}] ${map.get(comp_id)} -> ${comp_id}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function printRings(structure: Structure) {
|
||||
console.log('\nRings\n=============');
|
||||
for (const unit of structure.units) {
|
||||
@@ -221,7 +209,6 @@ async function run(frame: CifFrame, args: Args) {
|
||||
if (args.rings) printRings(structure);
|
||||
if (args.intraBonds) printBonds(structure, true, false);
|
||||
if (args.interBonds) printBonds(structure, false, true);
|
||||
if (args.mod) printModRes(models[0]);
|
||||
if (args.sec) printSecStructure(models[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,34 +4,31 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction } from '../../../../mol-state';
|
||||
import { StateAction, StateBuilder, StateTransformer } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin/state/objects';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Ingredient, CellPacking, Cell } from './data';
|
||||
import { Ingredient, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
|
||||
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
|
||||
import { Task, RuntimeContext } from '../../../../mol-task';
|
||||
import { StateTransforms } from '../../../../mol-plugin-state/transforms';
|
||||
import { distinctColors } from '../../../../mol-util/color/distinct';
|
||||
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
|
||||
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../../../mol-script/runtime/query/compiler';
|
||||
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
|
||||
import { ThemeRegistryContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme } from '../../../../mol-theme/color';
|
||||
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingsPreset } from './preset';
|
||||
import { AjaxTask } from '../../../../mol-util/data-source';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`
|
||||
@@ -113,7 +110,7 @@ function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`
|
||||
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } })
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op)
|
||||
}
|
||||
@@ -272,209 +269,131 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
|
||||
}
|
||||
offsetInvariantId += maxInvariantId
|
||||
offsetInvariantId += maxInvariantId + 1
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
|
||||
const s = builder.getStructure()
|
||||
for( let i = 0, il = s.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = s.models[i]
|
||||
trajectoryInfo.size = il
|
||||
trajectoryInfo.index = i
|
||||
}
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const RepresentationOptions = PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid'] as const)
|
||||
type RepresentationName = (typeof RepresentationOptions)[0][0]
|
||||
async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(ctx.runtime);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack Model' },
|
||||
params: {
|
||||
id: PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
['curveTest', 'Curve Test'],
|
||||
] as const),
|
||||
source: PD.MappedStatic('id', {
|
||||
'id': PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const),
|
||||
'file': PD.File({ accept: 'id' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
// id: PD.Select('influenza_model1.json', [
|
||||
// ['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
// ['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
// ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
// ['influenza_model1.json', 'influenza_model1'],
|
||||
// ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
// ['curveTest', 'Curve Test'],
|
||||
// ] as const),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('spacefill', RepresentationOptions)
|
||||
representation: PD.Select('spacefill', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid']))
|
||||
}, { isExpanded: true })
|
||||
},
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
const url = getCellPackModelUrl(params.id, params.baseUrl)
|
||||
|
||||
const root = state.build().toRoot();
|
||||
|
||||
let cellPackBuilder: any
|
||||
|
||||
if (params.id === 'curveTest') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
const cell: Cell = {
|
||||
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
|
||||
compartments: {
|
||||
'CurveCompartment': {
|
||||
interior: {
|
||||
ingredients: {
|
||||
'CurveIngredient': {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>
|
||||
if (params.source.name === 'id') {
|
||||
const url = getCellPackModelUrl(params.source.params, params.baseUrl)
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } })
|
||||
} else {
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
|
||||
const file = params.source.params
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } })
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
|
||||
const { packings } = cellPackObject.data
|
||||
const tree = state.build().to(cellPackBuilder.ref);
|
||||
|
||||
const isHiv = (
|
||||
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
|
||||
params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
|
||||
)
|
||||
// TODO make configurable
|
||||
// const isHiv = params.source.name === 'id' && (
|
||||
// params.source.params === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
|
||||
// params.source.params === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
|
||||
// )
|
||||
const isHiv = false
|
||||
|
||||
if (isHiv) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
await handleHivRna({ runtime: taskCtx, fetch: ctx.fetch }, packings, params.baseUrl)
|
||||
}
|
||||
|
||||
const colors = distinctColors(packings.length)
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[i])
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
|
||||
const p = { packing: i, baseUrl: params.baseUrl }
|
||||
|
||||
let cellpackTree = tree.apply(StructureFromCellpack, p)
|
||||
if (params.preset.traceOnly) {
|
||||
const expression = MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
|
||||
])
|
||||
})
|
||||
cellpackTree = cellpackTree.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }, { state: { isGhost: true } }) as any
|
||||
const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p)
|
||||
await ctx.updateDataState(packing, { revertOnError: true });
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
representation: params.preset.representation,
|
||||
hue
|
||||
}
|
||||
cellpackTree
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
color: getColorParams(hue)
|
||||
})
|
||||
)
|
||||
await CellpackPackingsPreset.apply(packing.selector, packingParams, ctx)
|
||||
}
|
||||
|
||||
if (isHiv) {
|
||||
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
color: UniformColorThemeProvider
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
console.time('cellpack')
|
||||
await state.updateTree(tree).runInContext(taskCtx);
|
||||
console.timeEnd('cellpack')
|
||||
}));
|
||||
|
||||
function getReprParams(ctx: PluginContext, params: { representation: RepresentationName, traceOnly: boolean }) {
|
||||
const { representation, traceOnly } = params
|
||||
switch (representation) {
|
||||
case 'spacefill':
|
||||
return traceOnly
|
||||
? [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ sizeFactor: 2, ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
: [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
case 'gaussian-surface':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('gaussian-surface'),
|
||||
() => ({
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
ignoreHydrogens: true
|
||||
})
|
||||
] as [any, any]
|
||||
case 'point':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('point'),
|
||||
() => ({ ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
case 'ellipsoid':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('orientation'),
|
||||
() => ({})
|
||||
] as [any, any]
|
||||
}
|
||||
}
|
||||
|
||||
function getColorParams(hue: [number, number]) {
|
||||
return [
|
||||
ModelIndexColorThemeProvider,
|
||||
(c: ColorTheme.Provider<any>, ctx: ThemeRegistryContext) => {
|
||||
return {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as [any, any]
|
||||
}
|
||||
// TODO
|
||||
// if (isHiv) {
|
||||
// const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
// tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
// ...getReprParams(ctx, params.preset),
|
||||
// color: UniformColorThemeProvider
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
}));
|
||||
71
src/apps/viewer/extensions/cellpack/preset.ts
Normal file
71
src/apps/viewer/extensions/cellpack/preset.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent, presetSelectionComponent } from '../../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
|
||||
export const CellpackPackingsPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
hue: PD.Interval([0, 360])
|
||||
}
|
||||
export type CellpackPackingsPresetParams = PD.ValuesFor<typeof CellpackPackingsPresetParams>
|
||||
|
||||
export const CellpackPackingsPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-cellpack',
|
||||
display: { name: 'CellPack' },
|
||||
params: () => CellpackPackingsPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const reprProps = Object.create(null);
|
||||
const components = Object.create(null);
|
||||
|
||||
let selectionType = 'polymer'
|
||||
|
||||
if (params.traceOnly) {
|
||||
selectionType = 'trace'
|
||||
components.polymer = await presetSelectionComponent(plugin, structureCell, 'trace')
|
||||
} else {
|
||||
components.polymer = await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
}
|
||||
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
ignoreHydrogens: true
|
||||
})
|
||||
} else if (params.representation === 'spacefill') {
|
||||
if (params.traceOnly) {
|
||||
Object.assign(reprProps, { sizeFactor: 2, ignoreHydrogens: true })
|
||||
} else {
|
||||
Object.assign(reprProps, { ignoreHydrogens: true })
|
||||
}
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const color = 'model-index'
|
||||
const colorParams = {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue: params.hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color, colorParams }, { tag: selectionType })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
@@ -1,17 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin/state/objects';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
|
||||
// export const DefaultCellPackBaseUrl = 'https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/cellPACK_database_1.1.0/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mgldev.scripps.edu/projects/autoPACK/web/cellpackproject/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// import { UUID } from '../../../mol-util';
|
||||
// import { ColorNames } from '../../../mol-util/color/names';
|
||||
// import { Camera } from '../../../mol-canvas3d/camera';
|
||||
// import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
// import { createStructureRepresentation3dParamss } from '../../../mol-plugin/state/transforms/representation';
|
||||
// import { createDefaultStructureComplex } from '../../../mol-plugin/util/structure-complex-helper';
|
||||
|
||||
// export const CreateJoleculeState = StateAction.build({
|
||||
@@ -108,13 +108,13 @@
|
||||
// group
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: MS.struct.modifier.wholeResidues([ expression ]), label: 'Residue' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
|
||||
// createStructureRepresentation3dParamss.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
|
||||
// }
|
||||
// if (params.e.selected && params.e.selected.length > 0) {
|
||||
// b.to(template.structure)
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: createExpression(params.e.selected), label: `Selected` })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick'));
|
||||
// createStructureRepresentation3dParamss.getDefaultParamsStatic(plugin, 'ball-and-stick'));
|
||||
// }
|
||||
// // TODO
|
||||
// // for (const l of params.e.distances) {
|
||||
|
||||
@@ -10,10 +10,12 @@ import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import './favicon.ico'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { LoadCellPackModel } from './extensions/cellpack/model';
|
||||
import { StructureFromCellpack } from './extensions/cellpack/state';
|
||||
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
@@ -45,8 +47,10 @@ function init() {
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
};
|
||||
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
|
||||
const plugin = createPlugin(document.getElementById('app')!, spec);
|
||||
trySetSnapshot(plugin);
|
||||
tryLoadFromUrl(plugin);
|
||||
}
|
||||
|
||||
async function trySetSnapshot(ctx: PluginContext) {
|
||||
@@ -58,11 +62,41 @@ async function trySetSnapshot(ctx: PluginContext) {
|
||||
const url = snapshotId
|
||||
? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}`
|
||||
: snapshotUrl;
|
||||
await PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url })
|
||||
await PluginCommands.State.Snapshots.Fetch(ctx, { url })
|
||||
} catch (e) {
|
||||
ctx.log.error('Failed to load snapshot.');
|
||||
console.warn('Failed to load snapshot', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
const url = getParam('loadFromURL', '[^&]+').trim();
|
||||
try {
|
||||
if (!url) return;
|
||||
|
||||
let format = 'cif', isBinary = false;
|
||||
switch (getParam('loadFromURLFormat', '[a-z]+').toLocaleLowerCase().trim()) {
|
||||
case 'pdb': format = 'pdb'; break;
|
||||
case 'mmbcif': isBinary = true; break;
|
||||
}
|
||||
|
||||
const params = DownloadStructure.createDefaultParams(void 0 as any, ctx);
|
||||
|
||||
return ctx.runTask(ctx.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url,
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
} catch (e) {
|
||||
ctx.log.error(`Failed to load from URL (${url})`);
|
||||
console.warn(`Failed to load from URL (${url})`, e);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -94,13 +94,13 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
}
|
||||
}
|
||||
|
||||
const ProteopediaCustomColorThemeProvider: ColorTheme.Provider<ProteopediaCustomColorThemeParams> = {
|
||||
return {
|
||||
name: 'proteopedia-custom',
|
||||
label: 'Proteopedia Custom',
|
||||
category: 'Custom',
|
||||
factory: ProteopediaCustomColorTheme,
|
||||
getParams: getChainIdColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ProteopediaCustomColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
}
|
||||
|
||||
return ProteopediaCustomColorThemeProvider;
|
||||
}
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { ResidueIndex, Model } from '../../mol-model/structure';
|
||||
import { BuiltInStructureRepresentationsName } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemeName } from '../../mol-theme/color';
|
||||
import { AminoAcidNames } from '../../mol-model/structure/model/types';
|
||||
import { StructureRepresentationRegistry } from '../../mol-repr/structure/registry';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { PolymerType } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
|
||||
@@ -54,15 +54,14 @@ export namespace ModelInfo {
|
||||
const hetMap = new Map<string, ModelInfo['hetResidues'][0]>();
|
||||
|
||||
for (let rI = 0 as ResidueIndex; rI < residueCount; rI++) {
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
if (AminoAcidNames.has(comp_id)) continue;
|
||||
const mod_parent = model.properties.modifiedResidues.parentId.get(comp_id);
|
||||
if (mod_parent && AminoAcidNames.has(mod_parent)) continue;
|
||||
if (model.atomicHierarchy.derived.residue.polymerType[rI] !== PolymerType.NA) continue;
|
||||
|
||||
const cI = chainIndex[residueOffsets[rI]];
|
||||
const eI = model.atomicHierarchy.index.getEntityFromChain(cI);
|
||||
if (model.entities.data.type.value(eI) === 'water') continue;
|
||||
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
|
||||
let lig = hetMap.get(comp_id);
|
||||
if (!lig) {
|
||||
lig = { name: comp_id, indices: [] };
|
||||
@@ -99,7 +98,7 @@ export interface RepresentationStyle {
|
||||
}
|
||||
|
||||
export namespace RepresentationStyle {
|
||||
export type Entry = { hide?: boolean, kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName }
|
||||
export type Entry = { hide?: boolean, kind?: StructureRepresentationRegistry.BuiltIn, coloring?: ColorTheme.BuiltIn }
|
||||
}
|
||||
|
||||
export enum StateElements {
|
||||
|
||||
@@ -172,16 +172,17 @@
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
addControl('Create Snapshot', () => {
|
||||
addControl('Set Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.get();
|
||||
// could use JSON.stringify(snapshot) and upload the data
|
||||
// console.log(JSON.stringify(snapshot, null, 2));
|
||||
});
|
||||
addControl('Apply Snapshot', () => {
|
||||
addControl('Restore Snapshot', () => {
|
||||
if (!snapshot) return;
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
|
||||
// or download snapshot using fetch or ajax or whatever
|
||||
// or PluginWrapper.snapshot.download(url);
|
||||
});
|
||||
addControl('Download Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.download();
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -8,35 +8,31 @@ import * as ReactDOM from 'react-dom';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
|
||||
import { volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemes } from '../../mol-theme/color';
|
||||
import { BuiltInSizeThemes } from '../../mol-theme/size';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
require('../../mol-plugin-ui/skin/light.scss')
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 3;
|
||||
static VERSION_MINOR = 4;
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 0;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -58,23 +54,23 @@ class MolStarProteopediaWrapper {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
right: ControlsWrapper
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []);
|
||||
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring);
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.propertyProvider.descriptor.name, EvolutionaryConservation.colorThemeProvider!);
|
||||
this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(customColoring);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(EvolutionaryConservation.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
|
||||
this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.plugin.state.dataState;
|
||||
return this.plugin.state.data;
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
@@ -92,10 +88,15 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
private structure(assemblyId: string) {
|
||||
const model = this.state.build().to(StateElements.Model);
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
|
||||
const s = model
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
|
||||
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: StateElements.Het });
|
||||
@@ -118,9 +119,10 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.SequenceVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.sequence && style.sequence.kind) || 'cartoon',
|
||||
(style.sequence && style.sequence.coloring) || 'unit-index', structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.sequence && style.sequence.kind) || 'cartoon',
|
||||
color: (style.sequence && style.sequence.coloring) || 'unit-index'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,9 +135,10 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.HetVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
|
||||
(style.hetGroups && style.hetGroups.coloring), structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
|
||||
color: style.hetGroups && style.hetGroups.coloring
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +152,7 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.Het3DSNFG);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.Het3DSNFG, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, 'carbohydrate', void 0, structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, { type: 'carbohydrate' }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,9 +163,11 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.WaterVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.water && style.water.kind) || 'ball-and-stick',
|
||||
(style.water && style.water.coloring), structure, { alpha: 0.51 }));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.water && style.water.kind) || 'ball-and-stick',
|
||||
typeParams: { alpha: 0.51 },
|
||||
color: style.water && style.water.coloring
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,14 +191,14 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
private applyState(tree: StateBuilder) {
|
||||
return PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
return PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -202,7 +207,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
|
||||
await this.applyState(modelTree);
|
||||
const info = await this.doInfo(true);
|
||||
@@ -213,39 +218,45 @@ class MolStarProteopediaWrapper {
|
||||
const tree = state.build();
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: asmId || 'deposited' }
|
||||
}
|
||||
}
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
await this.applyState(tree);
|
||||
}
|
||||
|
||||
await this.updateStyle(representationStyle);
|
||||
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
Scheduler.setImmediate(() => PluginCommands.Camera.Reset.dispatch(this.plugin, { }));
|
||||
Scheduler.setImmediate(() => PluginCommands.Camera.Reset(this.plugin, { }));
|
||||
}
|
||||
|
||||
async updateStyle(style?: RepresentationStyle, partial?: boolean) {
|
||||
const tree = this.visual(style, partial);
|
||||
if (!tree) return;
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const renderer = this.plugin.canvas3d.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
viewport = {
|
||||
setSettings: (settings?: Canvas3DProps) => {
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: settings || DefaultCanvas3DParams
|
||||
});
|
||||
}
|
||||
@@ -253,10 +264,10 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { }),
|
||||
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
|
||||
// if (!options) {
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: {
|
||||
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
|
||||
// clip: DefaultCanvas3DParams.clip
|
||||
@@ -269,7 +280,7 @@ class MolStarProteopediaWrapper {
|
||||
// const props = this.plugin.canvas3d.props;
|
||||
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
|
||||
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
|
||||
// });
|
||||
// }
|
||||
@@ -299,7 +310,7 @@ class MolStarProteopediaWrapper {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
if (!params || !!params.sequence) {
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
@@ -308,7 +319,7 @@ class MolStarProteopediaWrapper {
|
||||
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +339,7 @@ class MolStarProteopediaWrapper {
|
||||
remove: () => {
|
||||
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
|
||||
if (!r) return;
|
||||
PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
PluginCommands.State.RemoveObject(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
if (this.experimentalDataElement) {
|
||||
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
|
||||
this.experimentalDataElement = void 0;
|
||||
@@ -339,12 +350,12 @@ class MolStarProteopediaWrapper {
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
|
||||
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
},
|
||||
focusFirst: async (compId: string) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
await PluginCommands.Camera.Reset(this.plugin, { });
|
||||
|
||||
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
|
||||
@@ -377,7 +388,7 @@ class MolStarProteopediaWrapper {
|
||||
// }
|
||||
// });
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
|
||||
const focus = (this.state.select(StateElements.HetGroupFocus)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sphere = focus.boundary.sphere;
|
||||
@@ -386,26 +397,24 @@ class MolStarProteopediaWrapper {
|
||||
// Vec3.normalize(position, position);
|
||||
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
|
||||
const radius = Math.max(sphere.radius, 5)
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius, radius);
|
||||
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius);
|
||||
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
|
||||
private createSurVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
return createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick',
|
||||
color: 'uniform', colorParams: { value: ColorNames.gray },
|
||||
size: 'uniform', sizeParams: { value: 0.33 }
|
||||
});
|
||||
}
|
||||
|
||||
private createCoreVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
return createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -416,10 +425,16 @@ class MolStarProteopediaWrapper {
|
||||
set: (snapshot: PluginState.Snapshot) => {
|
||||
return this.plugin.state.setSnapshot(snapshot);
|
||||
},
|
||||
download: async (url: string) => {
|
||||
download: () => {
|
||||
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2);
|
||||
const blob = new Blob([json], {type : 'application/json;charset=utf-8'});
|
||||
download(blob, `mol-star_state_${(name || getFormattedTime())}.json`)
|
||||
},
|
||||
fetch: async (url: string) => {
|
||||
try {
|
||||
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
|
||||
await this.plugin.state.setSnapshot(snapshot);
|
||||
// TODO: is this OK to test for snapshots from server?
|
||||
await this.plugin.state.setSnapshot(snapshot?.data?.entries?.[0]?.snapshot || snapshot);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -6,24 +6,11 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { CurrentObject, PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { AnimationControls } from '../../../mol-plugin-ui/state/animation';
|
||||
import { CameraSnapshots } from '../../../mol-plugin-ui/camera';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { TransformUpdaterControl } from '../../../mol-plugin-ui/state/update-transform';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
return <div className='msp-scrollable-container msp-right-controls'>
|
||||
<CurrentObject />
|
||||
<AnimationControls />
|
||||
<CameraSnapshots />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
|
||||
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export { Camera }
|
||||
|
||||
@@ -33,6 +34,7 @@ class Camera {
|
||||
zoom = 1
|
||||
|
||||
readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
|
||||
readonly stateChanged = new BehaviorSubject<Partial<Camera.Snapshot>>(this.state);
|
||||
|
||||
get position() { return this.state.position; }
|
||||
set position(v: Vec3) { Vec3.copy(this.state.position, v); }
|
||||
@@ -45,8 +47,8 @@ class Camera {
|
||||
|
||||
private prevProjection = Mat4.identity();
|
||||
private prevView = Mat4.identity();
|
||||
private deltaDirection = Vec3.zero();
|
||||
private newPosition = Vec3.zero();
|
||||
private deltaDirection = Vec3();
|
||||
private newPosition = Vec3();
|
||||
|
||||
update() {
|
||||
const snapshot = this.state as Camera.Snapshot;
|
||||
@@ -76,18 +78,25 @@ class Camera {
|
||||
|
||||
setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
|
||||
this.transition.apply(snapshot, durationMs);
|
||||
this.stateChanged.next(snapshot);
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radiusNear: number, radiusFar: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const fov = this.state.fov
|
||||
getTargetDistance(radius: number) {
|
||||
const r = Math.max(radius, 0.01)
|
||||
const { fov } = this.state
|
||||
const { width, height } = this.viewport
|
||||
const aspect = width / height
|
||||
const aspectFactor = (height < width ? 1 : aspect)
|
||||
const targetDistance = Math.abs((radiusNear / aspectFactor) / Math.sin(fov / 2))
|
||||
return Math.abs((r / aspectFactor) / Math.sin(fov / 2))
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01)
|
||||
const targetDistance = this.getTargetDistance(r)
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position)
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
|
||||
@@ -96,17 +105,16 @@ class Camera {
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
|
||||
state.target = Vec3.clone(target)
|
||||
state.radiusNear = radiusNear
|
||||
state.radiusFar = radiusFar
|
||||
state.radius = r
|
||||
state.position = Vec3.clone(this.newPosition)
|
||||
if (up) Vec3.matchDirection(state.up, up, state.up)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
focus(target: Vec3, radiusNear: number, radiusFar: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radiusNear > 0 && radiusFar > 0) {
|
||||
this.setState(this.getFocus(target, radiusNear, radiusFar, up, dir), durationMs);
|
||||
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radius > 0) {
|
||||
this.setState(this.getFocus(target, radius, up, dir), durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +130,6 @@ class Camera {
|
||||
this.viewport = viewport;
|
||||
Camera.copySnapshot(this.state, state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Camera {
|
||||
@@ -161,8 +168,8 @@ namespace Camera {
|
||||
up: Vec3.create(0, 1, 0),
|
||||
target: Vec3.create(0, 0, 0),
|
||||
|
||||
radiusNear: 10,
|
||||
radiusFar: 10,
|
||||
radius: 10,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
};
|
||||
@@ -176,8 +183,8 @@ namespace Camera {
|
||||
up: Vec3
|
||||
target: Vec3
|
||||
|
||||
radiusNear: number
|
||||
radiusFar: number
|
||||
radius: number
|
||||
radiusMax: number
|
||||
fog: number
|
||||
clipFar: boolean
|
||||
}
|
||||
@@ -192,8 +199,8 @@ namespace Camera {
|
||||
if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up);
|
||||
if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target);
|
||||
|
||||
if (typeof source.radiusNear !== 'undefined') out.radiusNear = source.radiusNear;
|
||||
if (typeof source.radiusFar !== 'undefined') out.radiusFar = source.radiusFar;
|
||||
if (typeof source.radius !== 'undefined') out.radius = source.radius;
|
||||
if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
|
||||
if (typeof source.fog !== 'undefined') out.fog = source.fog;
|
||||
if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
|
||||
|
||||
@@ -262,14 +269,16 @@ function updatePers(camera: Camera) {
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state
|
||||
if (radius < 0.01) radius = 0.01
|
||||
|
||||
const cDist = Vec3.distance(camera.position, camera.target)
|
||||
let near = cDist - radiusNear
|
||||
let far = cDist + (clipFar ? radiusNear : radiusFar)
|
||||
const normalizedFar = clipFar ? radius : radiusMax
|
||||
const cameraDistance = Vec3.distance(camera.position, camera.target)
|
||||
let near = cameraDistance - radius
|
||||
let far = cameraDistance + normalizedFar
|
||||
|
||||
const fogNearFactor = -(50 - fog) / 50
|
||||
let fogNear = cDist - (radiusNear * fogNearFactor)
|
||||
let fogNear = cameraDistance - (normalizedFar * fogNearFactor)
|
||||
let fogFar = far
|
||||
|
||||
if (mode === 'perspective') {
|
||||
@@ -281,6 +290,11 @@ function updateClip(camera: Camera) {
|
||||
far = Math.max(0, far)
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
// make sure near and far are not identical to avoid Infinity in the projection matrix
|
||||
far = near + 0.01
|
||||
}
|
||||
|
||||
camera.near = near;
|
||||
camera.far = far;
|
||||
camera.fogNear = fogNear;
|
||||
|
||||
@@ -17,24 +17,37 @@ class CameraTransitionManager {
|
||||
private start = 0;
|
||||
inTransition = false;
|
||||
private durationMs = 0;
|
||||
private source: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private current = Camera.createDefaultSnapshot();
|
||||
private _source: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _current = Camera.createDefaultSnapshot();
|
||||
|
||||
get source(): Readonly<Camera.Snapshot> { return this._source }
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target }
|
||||
|
||||
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
|
||||
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(to);
|
||||
if (!this.inTransition) {
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
Camera.copySnapshot(this._target, this.camera.state);
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this._target, to);
|
||||
|
||||
if (this._target.radius > this._target.radiusMax) {
|
||||
this._target.radius = this._target.radiusMax
|
||||
}
|
||||
|
||||
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(this._target);
|
||||
return;
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this.source, this.camera.state);
|
||||
Camera.copySnapshot(this.target, this.camera.state);
|
||||
Camera.copySnapshot(this.target, to);
|
||||
|
||||
this.inTransition = true;
|
||||
this.func = transition || CameraTransitionManager.defaultTransition;
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
}
|
||||
|
||||
tick(t: number) {
|
||||
@@ -52,12 +65,12 @@ class CameraTransitionManager {
|
||||
|
||||
const normalized = Math.min((this.t - this.start) / this.durationMs, 1);
|
||||
if (normalized === 1) {
|
||||
this.finish(this.target!);
|
||||
this.finish(this._target!);
|
||||
return;
|
||||
}
|
||||
|
||||
this.func(this.current, normalized, this.source, this.target);
|
||||
Camera.copySnapshot(this.camera.state, this.current);
|
||||
this.func(this._current, normalized, this._source, this._target);
|
||||
Camera.copySnapshot(this.camera.state, this._current);
|
||||
}
|
||||
|
||||
constructor(private camera: Camera) {
|
||||
@@ -79,9 +92,9 @@ namespace CameraTransitionManager {
|
||||
// Lerp target, position & radius
|
||||
Vec3.lerp(out.target, source.target, target.target, t);
|
||||
Vec3.lerp(out.position, source.position, target.position, t);
|
||||
out.radiusNear = lerp(source.radiusNear, target.radiusNear, t);
|
||||
out.radius = lerp(source.radius, target.radius, t);
|
||||
// TODO take change of `clipFar` into account
|
||||
out.radiusFar = lerp(source.radiusFar, target.radiusFar, t);
|
||||
out.radiusMax = lerp(source.radiusMax, target.radiusMax, t);
|
||||
|
||||
// Lerp fov & fog
|
||||
out.fov = lerp(source.fov, target.fov, t);
|
||||
|
||||
@@ -31,15 +31,27 @@ import { PixelData } from '../mol-util/image';
|
||||
import { readTexture } from '../mol-gl/compute/util';
|
||||
import { DrawPass } from './passes/draw';
|
||||
import { PickPass } from './passes/pick';
|
||||
import { Task } from '../mol-task';
|
||||
import { ImagePass, ImageProps } from './passes/image';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
import { CameraHelper, CameraHelperParams } from './helper/camera-helper';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
|
||||
cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
cameraClipFar: PD.Boolean(true),
|
||||
camera: PD.Group({
|
||||
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
|
||||
helper: PD.Group(CameraHelperParams, { isFlat: true })
|
||||
}, { pivot: 'mode' }),
|
||||
cameraFog: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show fog in the distance' }),
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
}, { pivot: 'radius' }),
|
||||
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
|
||||
@@ -62,9 +74,10 @@ interface Canvas3D {
|
||||
/**
|
||||
* This function must be called if animate() is not set up so that add/remove actions take place.
|
||||
*/
|
||||
commit(): void
|
||||
commit(isSynchronous?: boolean): void
|
||||
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
|
||||
clear(): void
|
||||
syncVisibility(): void
|
||||
|
||||
requestDraw(force?: boolean): void
|
||||
animate(): void
|
||||
@@ -77,7 +90,7 @@ interface Canvas3D {
|
||||
|
||||
handleResize(): void
|
||||
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
|
||||
requestCameraReset(): void
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
|
||||
readonly camera: Camera
|
||||
readonly boundingSphere: Readonly<Sphere3D>
|
||||
downloadScreenshot(): void
|
||||
@@ -95,13 +108,12 @@ interface Canvas3D {
|
||||
}
|
||||
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
const DefaultRunTask = (task: Task<unknown>) => task.run()
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
@@ -146,10 +158,10 @@ namespace Canvas3D {
|
||||
if (isDebugMode) console.log('context restored')
|
||||
}, false)
|
||||
|
||||
return Canvas3D.create(webgl, input, props, runTask)
|
||||
return Canvas3D.create(webgl, input, props)
|
||||
}
|
||||
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props }
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
|
||||
@@ -168,17 +180,18 @@ namespace Canvas3D {
|
||||
|
||||
const camera = new Camera({
|
||||
position: Vec3.create(0, 0, 100),
|
||||
mode: p.cameraMode,
|
||||
fog: p.cameraFog,
|
||||
clipFar: p.cameraClipFar
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far
|
||||
})
|
||||
|
||||
const controls = TrackballControls.create(input, camera, p.trackball)
|
||||
const renderer = Renderer.create(webgl, p.renderer)
|
||||
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
|
||||
const cameraHelper = new CameraHelper(webgl, p.camera.helper);
|
||||
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, cameraHelper)
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
|
||||
@@ -190,6 +203,8 @@ namespace Canvas3D {
|
||||
|
||||
let drawPending = false
|
||||
let cameraResetRequested = false
|
||||
let nextCameraResetDuration: number | undefined = void 0
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0
|
||||
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci
|
||||
@@ -266,9 +281,7 @@ namespace Canvas3D {
|
||||
|
||||
function animate() {
|
||||
currentTime = now();
|
||||
|
||||
commit();
|
||||
|
||||
camera.transition.tick(currentTime);
|
||||
|
||||
draw(false);
|
||||
@@ -282,25 +295,73 @@ namespace Canvas3D {
|
||||
return webgl.isContextLost ? undefined : pickPass.identify(x, y)
|
||||
}
|
||||
|
||||
function commit() {
|
||||
commitScene();
|
||||
resolveCameraReset();
|
||||
function commit(isSynchronous: boolean = false) {
|
||||
const allCommited = commitScene(isSynchronous);
|
||||
// Only reset the camera after the full scene has been commited.
|
||||
if (allCommited) resolveCameraReset();
|
||||
}
|
||||
|
||||
function resolveCameraReset() {
|
||||
if (!cameraResetRequested) return;
|
||||
const { center, radius } = scene.boundingSphere;
|
||||
camera.focus(center, radius, radius, p.cameraResetDurationMs);
|
||||
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
|
||||
const focus = camera.getFocus(center, radius);
|
||||
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
|
||||
camera.setState(snapshot, duration);
|
||||
}
|
||||
|
||||
nextCameraResetDuration = void 0;
|
||||
nextCameraResetSnapshot = void 0;
|
||||
cameraResetRequested = false;
|
||||
}
|
||||
|
||||
const sceneCommitTimeoutMs = 250;
|
||||
function commitScene() {
|
||||
if (!scene.needsCommit) return;
|
||||
const oldBoundingSphereVisible = Sphere3D();
|
||||
const cameraSphere = Sphere3D();
|
||||
|
||||
function shouldResetCamera() {
|
||||
if (camera.state.radiusMax === 0) return true;
|
||||
|
||||
if (camera.transition.inTransition || nextCameraResetSnapshot) return false;
|
||||
|
||||
let cameraSphereOverlapsNone = true
|
||||
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius)
|
||||
|
||||
// check if any renderable has moved outside of the old bounding sphere
|
||||
// and if no renderable is overlapping with the camera sphere
|
||||
for (const r of scene.renderables) {
|
||||
if (!r.state.visible) continue;
|
||||
|
||||
const b = r.values.boundingSphere.ref.value;
|
||||
if (!b.radius) continue;
|
||||
|
||||
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
|
||||
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
|
||||
}
|
||||
|
||||
return cameraSphereOverlapsNone;
|
||||
}
|
||||
|
||||
const sceneCommitTimeoutMs = 250;
|
||||
function commitScene(isSynchronous: boolean) {
|
||||
if (!scene.needsCommit) return true;
|
||||
|
||||
// snapshot the current bounding sphere of visible objects
|
||||
Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
|
||||
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
|
||||
|
||||
const allCommited = scene.commit(sceneCommitTimeoutMs);
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
if (allCommited) reprCount.next(reprRenderObjects.size);
|
||||
if (reprCount.value === 0 || shouldResetCamera()) {
|
||||
cameraResetRequested = true;
|
||||
}
|
||||
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
|
||||
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0)
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
@@ -375,6 +436,16 @@ namespace Canvas3D {
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
},
|
||||
syncVisibility: () => {
|
||||
if (camera.state.radiusMax === 0) {
|
||||
cameraResetRequested = true
|
||||
nextCameraResetDuration = 0
|
||||
}
|
||||
|
||||
if (scene.syncVisibility()) {
|
||||
if (debugHelper.isEnabled) debugHelper.update()
|
||||
}
|
||||
},
|
||||
|
||||
// draw,
|
||||
requestDraw,
|
||||
@@ -384,7 +455,9 @@ namespace Canvas3D {
|
||||
getLoci,
|
||||
|
||||
handleResize,
|
||||
requestCameraReset: () => {
|
||||
requestCameraReset: options => {
|
||||
nextCameraResetDuration = options?.durationMs;
|
||||
nextCameraResetSnapshot = options?.snapshot;
|
||||
cameraResetRequested = true;
|
||||
},
|
||||
camera,
|
||||
@@ -404,15 +477,29 @@ namespace Canvas3D {
|
||||
didDraw,
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
|
||||
camera.setState({ mode: props.cameraMode })
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null)
|
||||
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
|
||||
cameraState.mode = props.camera.mode
|
||||
}
|
||||
if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) {
|
||||
camera.setState({ fog: props.cameraFog })
|
||||
if (props.cameraFog !== undefined) {
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
|
||||
if (newFog !== camera.state.fog) cameraState.fog = newFog
|
||||
}
|
||||
if (props.cameraClipFar !== undefined && props.cameraClipFar !== camera.state.clipFar) {
|
||||
camera.setState({ clipFar: props.cameraClipFar })
|
||||
if (props.cameraClipping !== undefined) {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius)
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
// if radius = 0, NaNs happen
|
||||
cameraState.radius = Math.max(radius, 0.01)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
|
||||
|
||||
if (props.camera?.helper) cameraHelper.setProps(props.camera.helper)
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
|
||||
|
||||
@@ -421,17 +508,27 @@ namespace Canvas3D {
|
||||
if (props.renderer) renderer.setProps(props.renderer)
|
||||
if (props.trackball) controls.setProps(props.trackball)
|
||||
if (props.debug) debugHelper.setProps(props.debug)
|
||||
|
||||
requestDraw(true)
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props)
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, cameraHelper, props)
|
||||
},
|
||||
|
||||
get props() {
|
||||
const radius = scene.boundingSphere.radius > 0
|
||||
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
|
||||
: 0
|
||||
|
||||
return {
|
||||
cameraMode: camera.state.mode,
|
||||
cameraFog: camera.state.fog,
|
||||
cameraClipFar: camera.state.clipFar,
|
||||
camera: {
|
||||
mode: camera.state.mode,
|
||||
helper: { ...cameraHelper.props }
|
||||
},
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
transparentBackground: p.transparentBackground,
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ const M = ModifiersKeys
|
||||
const Trigger = Binding.Trigger
|
||||
|
||||
export const DefaultTrackballBindings = {
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate the 3D scene by dragging using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate the 3D scene around the z-axis by dragging using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan the 3D scene by dragging using ${triggers}'),
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate', 'Drag using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate around z-axis', 'Drag using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan', 'Drag using ${triggers}'),
|
||||
dragZoom: Binding.Empty,
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus the 3D scene by dragging using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom the 3D scene by dragging using ${triggers}'),
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus', 'Drag using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom', 'Drag using ${triggers}'),
|
||||
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom the 3D scene by scrolling using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Focus the 3D scene by scrolling using ${triggers}'),
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom', 'Scroll using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Clip', 'Scroll using ${triggers}'),
|
||||
scrollFocusZoom: Binding.Empty,
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TrackballControlsParams = {
|
||||
panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
|
||||
|
||||
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
|
||||
spinSpeed: PD.Numeric(1, { min: -100, max: 100, step: 1 }),
|
||||
spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
|
||||
|
||||
staticMoving: PD.Boolean(true, { isHidden: true }),
|
||||
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
|
||||
@@ -208,8 +208,8 @@ namespace TrackballControls {
|
||||
function focusCamera() {
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
|
||||
if (factor !== 0.0) {
|
||||
const radiusNear = Math.max(1, camera.state.radiusNear + 10 * factor)
|
||||
camera.setState({ radiusNear })
|
||||
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor)
|
||||
camera.setState({ radius })
|
||||
}
|
||||
|
||||
if (p.staticMoving) {
|
||||
@@ -248,10 +248,14 @@ namespace TrackballControls {
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure the distance between object and target is within the min/max distance */
|
||||
/**
|
||||
* Ensure the distance between object and target is within the min/max distance
|
||||
* and not too large compared to `camera.state.radiusMax`
|
||||
*/
|
||||
function checkDistances() {
|
||||
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
|
||||
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance)
|
||||
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, maxDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
@@ -343,7 +347,7 @@ namespace TrackballControls {
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2)
|
||||
if (dragFocusZoom) {
|
||||
const dist = Vec3.distance(camera.state.position, camera.state.target);
|
||||
camera.setState({ radiusNear: dist / 5 })
|
||||
camera.setState({ radius: dist / 5 })
|
||||
}
|
||||
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -20,9 +20,10 @@ import { ValueCell } from '../../mol-util';
|
||||
import { Geometry } from '../../mol-geo/geometry/geometry';
|
||||
|
||||
export const DebugHelperParams = {
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of instances.' }),
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show full scene bounding spheres.' }),
|
||||
visibleSceneBoundingSpheres: PD.Boolean(false, { description: 'Show visible scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible instances.' }),
|
||||
}
|
||||
export type DebugHelperParams = typeof DebugHelperParams
|
||||
export type DebugHelperProps = PD.Values<DebugHelperParams>
|
||||
@@ -37,6 +38,7 @@ export class BoundingSphereHelper {
|
||||
private objectsData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private instancesData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private sceneData: BoundingSphereData | undefined
|
||||
private visibleSceneData: BoundingSphereData | undefined
|
||||
|
||||
constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) {
|
||||
this.scene = Scene.create(ctx)
|
||||
@@ -45,27 +47,28 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
|
||||
update() {
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey)
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId)
|
||||
if (newSceneData) this.sceneData = newSceneData
|
||||
|
||||
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId)
|
||||
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData
|
||||
|
||||
this.parent.forEach((r, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato)
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
|
||||
if (newObjectData) this.objectsData.set(ro, newObjectData)
|
||||
|
||||
if (ro.type === 'mesh' || ro.type === 'lines' || ro.type === 'points') {
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
transform: ro.values.transform,
|
||||
extraTransform: ro.values.extraTransform,
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
})
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData)
|
||||
}
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, instanceMaterialId, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
transform: ro.values.transform,
|
||||
extraTransform: ro.values.extraTransform,
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
})
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData)
|
||||
})
|
||||
|
||||
this.objectsData.forEach((objectData, ro) => {
|
||||
@@ -90,6 +93,10 @@ export class BoundingSphereHelper {
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres
|
||||
}
|
||||
|
||||
if (this.visibleSceneData) {
|
||||
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres
|
||||
}
|
||||
|
||||
this.parent.forEach((_, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres
|
||||
@@ -106,7 +113,10 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this._props.sceneBoundingSpheres || this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
return (
|
||||
this._props.sceneBoundingSpheres || this._props.visibleSceneBoundingSpheres ||
|
||||
this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
)
|
||||
}
|
||||
get props() { return this._props as Readonly<DebugHelperProps> }
|
||||
|
||||
@@ -116,10 +126,10 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, transform?: TransformData) {
|
||||
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, materialId: number, transform?: TransformData) {
|
||||
if (!data || !Sphere3D.equals(data.boundingSphere, boundingSphere)) {
|
||||
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh)
|
||||
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, transform)
|
||||
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform)
|
||||
if (data) {
|
||||
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh))
|
||||
} else {
|
||||
@@ -133,12 +143,21 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
|
||||
const detail = 2
|
||||
const vertexCount = sphereVertexCount(detail)
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
|
||||
if (boundingSphere.radius) addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
|
||||
if (boundingSphere.radius) {
|
||||
addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) addSphere(builderState, e, 1.0, 0)
|
||||
}
|
||||
}
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
}
|
||||
|
||||
const boundingSphereHelberMaterialId = getNextMaterialId()
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, transform?: TransformData) {
|
||||
const sceneMaterialId = getNextMaterialId()
|
||||
const visibleSceneMaterialId = getNextMaterialId()
|
||||
const objectMaterialId = getNextMaterialId()
|
||||
const instanceMaterialId = getNextMaterialId()
|
||||
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform)
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, boundingSphereHelberMaterialId)
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, materialId)
|
||||
}
|
||||
181
src/mol-canvas3d/helper/camera-helper.ts
Normal file
181
src/mol-canvas3d/helper/camera-helper.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { Camera } from '../camera';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
// TODO add scale line/grid
|
||||
|
||||
const AxesParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
|
||||
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
|
||||
}
|
||||
type AxesParams = typeof AxesParams
|
||||
type AxesProps = PD.Values<AxesParams>
|
||||
|
||||
export const CameraHelperParams = {
|
||||
axes: PD.MappedStatic('on', {
|
||||
on: PD.Group(AxesParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show camera orientation axes' }),
|
||||
}
|
||||
export type CameraHelperParams = typeof CameraHelperParams
|
||||
export type CameraHelperProps = PD.Values<CameraHelperParams>
|
||||
|
||||
export class CameraHelper {
|
||||
scene: Scene
|
||||
camera: Camera
|
||||
props: CameraHelperProps = {
|
||||
axes: { name: 'off', params: {} }
|
||||
}
|
||||
|
||||
private renderObject: GraphicsRenderObject | undefined
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl)
|
||||
|
||||
this.camera = new Camera()
|
||||
Vec3.set(this.camera.up, 0, 1, 0)
|
||||
Vec3.set(this.camera.target, 0, 0, 0)
|
||||
|
||||
this.setProps(props)
|
||||
}
|
||||
|
||||
setProps(props: Partial<CameraHelperProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.axes !== undefined) {
|
||||
p.axes.name = props.axes.name
|
||||
if (props.axes.name === 'on') {
|
||||
this.scene.clear()
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio }
|
||||
this.renderObject = undefined
|
||||
createAxesRenderObject(params).then(renderObject => {
|
||||
this.renderObject = renderObject
|
||||
this.scene.add(this.renderObject)
|
||||
this.scene.commit()
|
||||
})
|
||||
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200)
|
||||
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up)
|
||||
|
||||
p.axes.params = { ...props.axes.params }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.props.axes.name === 'on'
|
||||
}
|
||||
|
||||
update(camera: Camera) {
|
||||
if (!this.renderObject) return
|
||||
|
||||
updateCamera(this.camera, camera.viewport)
|
||||
|
||||
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4
|
||||
Mat4.extractRotation(m, camera.view)
|
||||
|
||||
const r = this.renderObject.values.boundingSphere.ref.value.radius
|
||||
Mat4.setTranslation(m, Vec3.create(
|
||||
-camera.viewport.width / 2 + r,
|
||||
-camera.viewport.height / 2 + r,
|
||||
0
|
||||
))
|
||||
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value)
|
||||
this.scene.update([this.renderObject], true)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport) {
|
||||
const { near, far } = camera
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2
|
||||
const fullRight = (viewport.width - viewport.x) / 2
|
||||
const fullTop = (viewport.height - viewport.y) / 2
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2
|
||||
|
||||
const dx = (fullRight - fullLeft) / 2
|
||||
const dy = (fullTop - fullBottom) / 2
|
||||
const cx = (fullRight + fullLeft) / 2
|
||||
const cy = (fullTop + fullBottom) / 2
|
||||
|
||||
const left = cx - dx
|
||||
const right = cx + dx
|
||||
const top = cy + dy
|
||||
const bottom = cy - dy
|
||||
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
||||
}
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh)
|
||||
const radius = 0.05 * scale
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale)
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale)
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale)
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 }
|
||||
|
||||
state.currentGroup = 0
|
||||
addSphere(state, Vec3.origin, radius, 2)
|
||||
|
||||
state.currentGroup = 1
|
||||
addSphere(state, x, radius, 2)
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps)
|
||||
|
||||
state.currentGroup = 2
|
||||
addSphere(state, y, radius, 2)
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps)
|
||||
|
||||
state.currentGroup = 3
|
||||
addSphere(state, z, radius, 2)
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps)
|
||||
|
||||
return MeshBuilder.getMesh(state)
|
||||
}
|
||||
|
||||
function getAxesShape(ctx: RuntimeContext, data: {}, props: AxesProps, shape?: Shape<Mesh>) {
|
||||
const scale = 100 * props.scale
|
||||
const mesh = createAxesMesh(scale, shape?.geometry)
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4))
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
case 1: return props.colorX
|
||||
case 2: return props.colorY
|
||||
case 3: return props.colorZ
|
||||
default: return ColorNames.grey
|
||||
}
|
||||
}
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '')
|
||||
}
|
||||
|
||||
async function createAxesRenderObject(props: AxesProps) {
|
||||
const repr = ShapeRepresentation(getAxesShape, Mesh.Utils)
|
||||
await repr.createOrUpdate(props, {}).run()
|
||||
return repr.renderObjects[0]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,6 +11,7 @@ import Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Camera } from '../camera';
|
||||
import { CameraHelper } from '../helper/camera-helper';
|
||||
|
||||
export class DrawPass {
|
||||
colorTarget: RenderTarget
|
||||
@@ -19,7 +20,7 @@ export class DrawPass {
|
||||
|
||||
private depthTarget: RenderTarget | null
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private cameraHelper: CameraHelper) {
|
||||
const { gl, extensions, resources } = webgl
|
||||
const width = gl.drawingBufferWidth
|
||||
const height = gl.drawingBufferHeight
|
||||
@@ -43,7 +44,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
|
||||
const { webgl, renderer, colorTarget, depthTarget } = this
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
} else {
|
||||
@@ -55,21 +56,26 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
|
||||
renderer.render(scene, camera, 'color', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'color', false, transparentBackground)
|
||||
}
|
||||
this.renderInternal('color', transparentBackground)
|
||||
|
||||
// do a depth pass if not rendering to drawing buffer and
|
||||
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
|
||||
if (!toDrawingBuffer && depthTarget) {
|
||||
depthTarget.bind()
|
||||
renderer.render(scene, camera, 'depth', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'depth', false, transparentBackground)
|
||||
}
|
||||
this.renderInternal('depth', transparentBackground)
|
||||
}
|
||||
}
|
||||
|
||||
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this
|
||||
renderer.render(scene, camera, variant, true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground)
|
||||
}
|
||||
if (cameraHelper.isEnabled) {
|
||||
cameraHelper.update(camera)
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { PostprocessingPass, PostprocessingParams } from './postprocessing'
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
|
||||
import { Camera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { CameraHelper } from '../helper/camera-helper';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
@@ -39,12 +40,12 @@ export class ImagePass {
|
||||
get width() { return this._width }
|
||||
get height() { return this._height }
|
||||
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, cameraHelper: CameraHelper, props: Partial<ImageProps>) {
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props }
|
||||
|
||||
this._transparentBackground = p.transparentBackground
|
||||
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, cameraHelper)
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
|
||||
}
|
||||
|
||||
const schema = { ...ComposeSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, compose_frag)
|
||||
const shaderCode = ShaderCode('compose', quad_vert, compose_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -17,6 +17,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { DrawPass } from './draw';
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert'
|
||||
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag'
|
||||
@@ -47,14 +48,21 @@ const PostprocessingSchema = {
|
||||
}
|
||||
|
||||
export const PostprocessingParams = {
|
||||
occlusionEnable: PD.Boolean(false),
|
||||
occlusionKernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
|
||||
occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
|
||||
occlusionRadius: PD.Numeric(32, { min: 0, max: 256, step: 1 }),
|
||||
|
||||
outlineEnable: PD.Boolean(false),
|
||||
outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
|
||||
outlineThreshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
|
||||
occlusion: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
|
||||
bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
|
||||
radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
outline: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
|
||||
threshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Draw outline around 3D objects' })
|
||||
}
|
||||
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
|
||||
|
||||
@@ -75,20 +83,20 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uFogFar: ValueCell.create(10000),
|
||||
uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
|
||||
|
||||
dOcclusionEnable: ValueCell.create(p.occlusionEnable),
|
||||
dOcclusionKernelSize: ValueCell.create(p.occlusionKernelSize),
|
||||
uOcclusionBias: ValueCell.create(p.occlusionBias),
|
||||
uOcclusionRadius: ValueCell.create(p.occlusionRadius),
|
||||
dOcclusionEnable: ValueCell.create(p.occlusion.name === 'on'),
|
||||
dOcclusionKernelSize: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.kernelSize : 4),
|
||||
uOcclusionBias: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.bias : 0.5),
|
||||
uOcclusionRadius: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.radius : 64),
|
||||
|
||||
dOutlineEnable: ValueCell.create(p.outlineEnable),
|
||||
uOutlineScale: ValueCell.create(p.outlineScale * ctx.pixelRatio),
|
||||
uOutlineThreshold: ValueCell.create(p.outlineThreshold),
|
||||
dOutlineEnable: ValueCell.create(p.outline.name === 'on'),
|
||||
uOutlineScale: ValueCell.create((p.outline.name === 'on' ? p.outline.params.scale : 1) * ctx.pixelRatio),
|
||||
uOutlineThreshold: ValueCell.create(p.outline.name === 'on' ? p.outline.params.threshold : 0.8),
|
||||
|
||||
dPackedDepth: ValueCell.create(packedDepth),
|
||||
}
|
||||
|
||||
const schema = { ...PostprocessingSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, postprocessing_frag)
|
||||
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
@@ -108,7 +116,7 @@ export class PostprocessingPass {
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this.props.occlusionEnable || this.props.outlineEnable
|
||||
return this.props.occlusion.name === 'on' || this.props.outline.name === 'on'
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -117,35 +125,28 @@ export class PostprocessingPass {
|
||||
}
|
||||
|
||||
setProps(props: Partial<PostprocessingProps>) {
|
||||
if (props.occlusionEnable !== undefined) {
|
||||
this.props.occlusionEnable = props.occlusionEnable
|
||||
ValueCell.update(this.renderable.values.dOcclusionEnable, props.occlusionEnable)
|
||||
}
|
||||
if (props.occlusionKernelSize !== undefined) {
|
||||
this.props.occlusionKernelSize = props.occlusionKernelSize
|
||||
ValueCell.update(this.renderable.values.dOcclusionKernelSize, props.occlusionKernelSize)
|
||||
}
|
||||
if (props.occlusionBias !== undefined) {
|
||||
this.props.occlusionBias = props.occlusionBias
|
||||
ValueCell.update(this.renderable.values.uOcclusionBias, props.occlusionBias)
|
||||
}
|
||||
if (props.occlusionRadius !== undefined) {
|
||||
this.props.occlusionRadius = props.occlusionRadius
|
||||
ValueCell.update(this.renderable.values.uOcclusionRadius, props.occlusionRadius)
|
||||
}
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.occlusion !== undefined) {
|
||||
p.occlusion.name = props.occlusion.name
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, props.occlusion.name === 'on')
|
||||
if (props.occlusion.name === 'on') {
|
||||
p.occlusion.params = { ...props.occlusion.params }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionKernelSize, props.occlusion.params.kernelSize)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionBias, props.occlusion.params.bias)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionRadius, props.occlusion.params.radius)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.outlineEnable !== undefined) {
|
||||
this.props.outlineEnable = props.outlineEnable
|
||||
ValueCell.update(this.renderable.values.dOutlineEnable, props.outlineEnable)
|
||||
}
|
||||
if (props.outlineScale !== undefined) {
|
||||
this.props.outlineScale = props.outlineScale
|
||||
ValueCell.update(this.renderable.values.uOutlineScale, props.outlineScale * this.webgl.pixelRatio)
|
||||
}
|
||||
if (props.outlineThreshold !== undefined) {
|
||||
this.props.outlineThreshold = props.outlineThreshold
|
||||
ValueCell.update(this.renderable.values.uOutlineThreshold, props.outlineThreshold)
|
||||
}
|
||||
if (props.outline !== undefined) {
|
||||
p.outline.name = props.outline.name
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, props.outline.name === 'on')
|
||||
if (props.outline.name === 'on') {
|
||||
p.outline.params = { ...props.outline.params }
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.renderable.update()
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { sortArray } from '../util/sort'
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
|
||||
/** A collection of columns */
|
||||
type Table<Schema extends Table.Schema> = {
|
||||
type Table<Schema extends Table.Schema = any> = {
|
||||
readonly _rowCount: number,
|
||||
readonly _columns: ReadonlyArray<string>,
|
||||
readonly _schema: Schema
|
||||
@@ -73,7 +73,7 @@ namespace Table {
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R {
|
||||
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rows: ArrayLike<Partial<Row<S>>>): R {
|
||||
const ret = Object.create(null);
|
||||
const rowCount = rows.length;
|
||||
const columns = Object.keys(schema);
|
||||
@@ -91,14 +91,19 @@ namespace Table {
|
||||
return ret as R;
|
||||
}
|
||||
|
||||
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R {
|
||||
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: S, arrays: Partial<Arrays<S>>): R {
|
||||
const ret = Object.create(null);
|
||||
const columns = Object.keys(schema);
|
||||
ret._rowCount = arrays[columns[0]].length;
|
||||
ret._rowCount = 0;
|
||||
ret._columns = columns;
|
||||
ret._schema = schema;
|
||||
for (const k of columns) {
|
||||
(ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]);
|
||||
if (typeof arrays[k] !== 'undefined') {
|
||||
(ret as any)[k] = Column.ofArray({ array: arrays[k]!, schema: schema[k] });
|
||||
ret._rowCount = arrays[k]?.length;
|
||||
} else {
|
||||
(ret as any)[k] = Column.Undefined(ret._rowCount, schema[k]);
|
||||
}
|
||||
}
|
||||
return ret as R;
|
||||
}
|
||||
@@ -167,7 +172,7 @@ namespace Table {
|
||||
}
|
||||
|
||||
/** Sort and return a new table */
|
||||
export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) {
|
||||
export function sort<T extends Table>(table: T, cmp: (i: number, j: number) => number) {
|
||||
const indices = new Int32Array(table._rowCount);
|
||||
for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i;
|
||||
sortArray(indices, (_, i, j) => cmp(i, j));
|
||||
@@ -191,7 +196,7 @@ namespace Table {
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function areEqual<T extends Table<Schema>>(a: T, b: T) {
|
||||
export function areEqual<T extends Table<any>>(a: T, b: T) {
|
||||
if (a._rowCount !== b._rowCount) return false;
|
||||
if (a._columns.length !== b._columns.length) return false;
|
||||
for (const c of a._columns) {
|
||||
|
||||
@@ -28,18 +28,24 @@ export const VisualQualityInfo = {
|
||||
'lowest': {},
|
||||
}
|
||||
export type VisualQuality = keyof typeof VisualQualityInfo
|
||||
export const VisualQualityNames = Object.keys(VisualQualityInfo)
|
||||
export const VisualQualityOptions = VisualQualityNames.map(n => [n, n] as [VisualQuality, string])
|
||||
export const VisualQualityNames = Object.keys(VisualQualityInfo) as VisualQuality[]
|
||||
export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames)
|
||||
|
||||
//
|
||||
|
||||
export namespace BaseGeometry {
|
||||
export const Params = {
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity' }),
|
||||
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
|
||||
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
export const ShadingCategory: PD.Info = { category: 'Shading' };
|
||||
export const CustomQualityParamInfo: PD.Info = {
|
||||
category: 'Custom Quality',
|
||||
hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
|
||||
};
|
||||
|
||||
export type Counts = { drawCount: number, groupCount: number, instanceCount: number }
|
||||
|
||||
export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
|
||||
|
||||
@@ -4,29 +4,28 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ValueCell } from '../../../mol-util'
|
||||
import { Sphere3D, Box3D } from '../../../mol-math/geometry'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { Vec3, Mat4, Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { Box } from '../../primitive/box';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createColors } from '../color-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Mat4, Vec2, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Box } from '../../primitive/box';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createColors } from '../color-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
|
||||
const VolumeBox = Box()
|
||||
const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
|
||||
@@ -117,7 +116,7 @@ export namespace DirectVolume {
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
|
||||
]),
|
||||
list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
|
||||
list: PD.ColorList('red-yellow-blue'),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -148,7 +147,7 @@ export namespace DirectVolume {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.list)
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.list.colors)
|
||||
|
||||
const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 * 5
|
||||
|
||||
@@ -193,7 +192,7 @@ export namespace DirectVolume {
|
||||
ValueCell.updateIfChanged(values.dRenderMode, props.renderMode)
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
|
||||
createTransferFunctionTexture(controlPoints, props.list, values.tTransferTex)
|
||||
createTransferFunctionTexture(controlPoints, props.list.colors, values.tTransferTex)
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
import { ValueCell } from '../../../mol-util'
|
||||
import { Mat4 } from '../../../mol-math/linear-algebra'
|
||||
import { transformPositionArray,/* , transformDirectionArray, getNormalMatrix */
|
||||
GroupMapping,
|
||||
createGroupMapping} from '../../util';
|
||||
import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createColors } from '../color-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
@@ -50,6 +48,8 @@ export interface Lines {
|
||||
readonly boundingSphere: Sphere3D
|
||||
/** Maps group ids to line indices */
|
||||
readonly groupMapping: GroupMapping
|
||||
|
||||
setBoundingSphere(boundingSphere: Sphere3D): void
|
||||
}
|
||||
|
||||
export namespace Lines {
|
||||
@@ -129,6 +129,10 @@ export namespace Lines {
|
||||
currentGroup = lines.groupBuffer.ref.version
|
||||
}
|
||||
return groupMapping
|
||||
},
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere)
|
||||
currentHash = hashCode(lines)
|
||||
}
|
||||
}
|
||||
return lines
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface Mesh {
|
||||
readonly boundingSphere: Sphere3D
|
||||
/** Maps group ids to vertex indices */
|
||||
readonly groupMapping: GroupMapping
|
||||
|
||||
setBoundingSphere(boundingSphere: Sphere3D): void
|
||||
}
|
||||
|
||||
export namespace Mesh {
|
||||
@@ -101,6 +103,10 @@ export namespace Mesh {
|
||||
currentGroup = mesh.groupBuffer.ref.version
|
||||
}
|
||||
return groupMapping
|
||||
},
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere)
|
||||
currentHash = hashCode(mesh)
|
||||
}
|
||||
}
|
||||
return mesh
|
||||
@@ -321,10 +327,10 @@ export namespace Mesh {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
doubleSided: PD.Boolean(false),
|
||||
flipSided: PD.Boolean(false),
|
||||
flatShaded: PD.Boolean(false),
|
||||
ignoreLight: PD.Boolean(false),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
import { ValueCell } from '../../../mol-util'
|
||||
import { Mat4 } from '../../../mol-math/linear-algebra'
|
||||
import { transformPositionArray,/* , transformDirectionArray, getNormalMatrix */
|
||||
GroupMapping,
|
||||
createGroupMapping} from '../../util';
|
||||
import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createColors } from '../color-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
@@ -43,6 +41,8 @@ export interface Points {
|
||||
readonly boundingSphere: Sphere3D
|
||||
/** Maps group ids to point indices */
|
||||
readonly groupMapping: GroupMapping
|
||||
|
||||
setBoundingSphere(boundingSphere: Sphere3D): void
|
||||
}
|
||||
|
||||
export namespace Points {
|
||||
@@ -92,6 +92,10 @@ export namespace Points {
|
||||
currentGroup = points.groupBuffer.ref.version
|
||||
}
|
||||
return groupMapping
|
||||
},
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere)
|
||||
currentHash = hashCode(points)
|
||||
}
|
||||
}
|
||||
return points
|
||||
|
||||
@@ -42,6 +42,8 @@ export interface Spheres {
|
||||
readonly boundingSphere: Sphere3D
|
||||
/** Maps group ids to sphere indices */
|
||||
readonly groupMapping: GroupMapping
|
||||
|
||||
setBoundingSphere(boundingSphere: Sphere3D): void
|
||||
}
|
||||
|
||||
export namespace Spheres {
|
||||
@@ -97,6 +99,10 @@ export namespace Spheres {
|
||||
currentGroup = spheres.groupBuffer.ref.version
|
||||
}
|
||||
return groupMapping
|
||||
},
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere)
|
||||
currentHash = hashCode(spheres)
|
||||
}
|
||||
}
|
||||
return spheres
|
||||
@@ -114,8 +120,8 @@ export namespace Spheres {
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
|
||||
doubleSided: PD.Boolean(false),
|
||||
ignoreLight: PD.Boolean(false),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ export interface Text {
|
||||
readonly boundingSphere: Sphere3D
|
||||
/** Maps group ids to text indices */
|
||||
readonly groupMapping: GroupMapping
|
||||
|
||||
setBoundingSphere(boundingSphere: Sphere3D): void
|
||||
}
|
||||
|
||||
export namespace Text {
|
||||
@@ -124,6 +126,10 @@ export namespace Text {
|
||||
currentGroup = text.groupBuffer.ref.version
|
||||
}
|
||||
return groupMapping
|
||||
},
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere)
|
||||
currentHash = hashCode(text)
|
||||
}
|
||||
}
|
||||
return text
|
||||
|
||||
@@ -70,9 +70,9 @@ export namespace TextureMesh {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
doubleSided: PD.Boolean(false),
|
||||
flipSided: PD.Boolean(false),
|
||||
flatShaded: PD.Boolean(false),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -166,6 +166,19 @@ class MarchingCubesState {
|
||||
const v1 = sfg(sf, hi, hj, hk);
|
||||
const t = (this.isoLevel - v0) / (v0 - v1);
|
||||
|
||||
if (this.idField) {
|
||||
const u = this.idFieldGet!(this.idField, li, lj, lk);
|
||||
const v = this.idFieldGet!(this.idField, hi, hj, hk)
|
||||
let a = t < 0.5 ? u : v;
|
||||
// -1 means 'no id', check if the other cell has an id
|
||||
if (a === -1) a = t < 0.5 ? v : u;
|
||||
// -2 means 'ignore this cell'
|
||||
if (a === -2) return -1
|
||||
this.builder.addGroup(a);
|
||||
} else {
|
||||
this.builder.addGroup(0);
|
||||
}
|
||||
|
||||
const id = this.builder.addVertex(
|
||||
li + t * (li - hi),
|
||||
lj + t * (lj - hj),
|
||||
@@ -189,16 +202,6 @@ class MarchingCubesState {
|
||||
n0z + t * (n0z - n1z)
|
||||
)
|
||||
|
||||
if (this.idField) {
|
||||
const u = this.idFieldGet!(this.idField, li, lj, lk);
|
||||
const v = this.idFieldGet!(this.idField, hi, hj, hk)
|
||||
let a = t < 0.5 ? u : v;
|
||||
if (a < 0) a = t < 0.5 ? v : u;
|
||||
this.builder.addGroup(a);
|
||||
} else {
|
||||
this.builder.addGroup(0);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,13 @@ export function MarchinCubesMeshBuilder(vertexChunkSize: number, mesh?: Mesh): M
|
||||
ChunkedArray.add(groups, group);
|
||||
},
|
||||
addTriangle: (vertList: number[], a: number, b: number, c: number) => {
|
||||
++triangleCount
|
||||
ChunkedArray.add3(indices, vertList[a], vertList[b], vertList[c]);
|
||||
const i = vertList[a], j = vertList[b], k = vertList[c]
|
||||
// vertex indices <0 mean that the vertex was ignored and is not available
|
||||
// and hence we don't add a triangle when this occurs
|
||||
if (i >= 0 && j >= 0 && k >= 0) {
|
||||
++triangleCount
|
||||
ChunkedArray.add3(indices, i, j, k)
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
const vb = ChunkedArray.compact(vertices, true) as Float32Array;
|
||||
@@ -73,17 +78,22 @@ export function MarchinCubesLinesBuilder(vertexChunkSize: number, lines?: Lines)
|
||||
ChunkedArray.add(groups, group);
|
||||
},
|
||||
addTriangle: (vertList: number[], a: number, b: number, c: number, edgeFilter: number) => {
|
||||
if (AllowedContours[a][b] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[b])
|
||||
}
|
||||
if (AllowedContours[b][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[b], vertList[c])
|
||||
}
|
||||
if (AllowedContours[a][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[c])
|
||||
const i = vertList[a], j = vertList[b], k = vertList[c]
|
||||
// vertex indices <0 mean that the vertex was ignored and is not available
|
||||
// and hence we don't add a triangle when this occurs
|
||||
if (i >= 0 && j >= 0 && k >= 0) {
|
||||
if (AllowedContours[a][b] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[b])
|
||||
}
|
||||
if (AllowedContours[b][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[b], vertList[c])
|
||||
}
|
||||
if (AllowedContours[a][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[c])
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -10,7 +10,8 @@ describe('renderable', () => {
|
||||
it('calculateBoundingSphere', () => {
|
||||
const position = new Float32Array([
|
||||
0, 0, 0,
|
||||
1, 0, 0
|
||||
1, 0, 0,
|
||||
-1, 0, 0,
|
||||
])
|
||||
const transform = new Float32Array([
|
||||
1, 0, 0, 0,
|
||||
@@ -26,15 +27,18 @@ describe('renderable', () => {
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
2, 0, 0, 0
|
||||
-1, 0, 0, 0
|
||||
])
|
||||
|
||||
const { boundingSphere } = calculateBoundingSphere(
|
||||
const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
|
||||
position, position.length / 3,
|
||||
transform, transform.length / 16
|
||||
)
|
||||
|
||||
expect(boundingSphere.radius).toBe(1.5)
|
||||
expect(boundingSphere.center).toEqual([1.5, 0.0, 0.0])
|
||||
expect(invariantBoundingSphere.extrema).toEqual([[0, 0, 0], [1, 0, 0], [-1, 0, 0]])
|
||||
expect(invariantBoundingSphere.radius).toBe(1)
|
||||
expect(invariantBoundingSphere.center).toEqual([0, 0, 0])
|
||||
expect(boundingSphere.radius).toBe(2)
|
||||
expect(boundingSphere.center).toEqual([0, 0, 0])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,7 +41,7 @@ function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: T
|
||||
}
|
||||
|
||||
const schema = { ...HistopyramidReductionSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, reduction_frag)
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
|
||||
|
||||
@@ -34,7 +34,7 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
}
|
||||
|
||||
const schema = { ...HistopyramidSumSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, sum_frag)
|
||||
const shaderCode = ShaderCode('sum', quad_vert, sum_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
HistopyramidSumRenderable = createComputeRenderable(renderItem, values)
|
||||
|
||||
@@ -46,7 +46,7 @@ function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridD
|
||||
}
|
||||
|
||||
const schema = { ...ActiveVoxelsSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, active_voxels_frag)
|
||||
const shaderCode = ShaderCode('active-voxels', quad_vert, active_voxels_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
|
||||
@@ -62,7 +62,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
}
|
||||
|
||||
const schema = { ...IsosurfaceSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, isosurface_frag, { drawBuffers: true })
|
||||
const shaderCode = ShaderCode('isosurface', quad_vert, isosurface_frag, { drawBuffers: true })
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -29,7 +29,7 @@ export interface TextureVolume<T extends Uint8Array | Float32Array> {
|
||||
readonly depth: number
|
||||
}
|
||||
|
||||
export function createTextureImage<T extends Uint8Array | Float32Array>(n: number, itemSize: number, arrayCtor: new (length: number) => T, array?: T): TextureImage<T> {
|
||||
export function createTextureImage<T extends Uint8Array | Float32Array>(n: number, itemSize: number, arrayCtor: new (length: number)=> T, array?: T): TextureImage<T> {
|
||||
const { length, width, height } = calculateTextureInfo(n, itemSize)
|
||||
array = array && array.length >= length ? array : new arrayCtor(length)
|
||||
return { array, width, height }
|
||||
@@ -66,7 +66,10 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
|
||||
img.src = objectURL
|
||||
img.style.width = imageData.width * scale + 'px'
|
||||
img.style.height = imageData.height * scale + 'px';
|
||||
if (pixelated) (img.style as any).imageRendering = 'pixelated' // supported only in Chrome
|
||||
if (pixelated) {
|
||||
// not supported in Firefox and IE
|
||||
img.style.imageRendering = 'pixelated'
|
||||
}
|
||||
img.style.position = 'absolute'
|
||||
img.style.top = '0px'
|
||||
img.style.left = '0px'
|
||||
@@ -78,35 +81,73 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
|
||||
//
|
||||
|
||||
const v = Vec3.zero()
|
||||
const boundaryHelper = new BoundaryHelper()
|
||||
const boundaryHelperCoarse = new BoundaryHelper('14')
|
||||
const boundaryHelperFine = new BoundaryHelper('98')
|
||||
|
||||
function getHelper(count: number) {
|
||||
return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
|
||||
}
|
||||
|
||||
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D {
|
||||
const step = stepFactor * 3
|
||||
boundaryHelper.reset(0)
|
||||
const boundaryHelper = getHelper(positionCount)
|
||||
|
||||
boundaryHelper.reset()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.boundaryStep(v, 0)
|
||||
boundaryHelper.includePosition(v)
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep()
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.extendStep(v, 0)
|
||||
boundaryHelper.radiusPosition(v)
|
||||
}
|
||||
return boundaryHelper.getSphere()
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
|
||||
if (positionCount <= 98) {
|
||||
const extrema: Vec3[] = []
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
extrema.push(Vec3.fromArray(Vec3(), position, i));
|
||||
}
|
||||
Sphere3D.setExtrema(sphere, extrema)
|
||||
}
|
||||
|
||||
return sphere
|
||||
}
|
||||
|
||||
export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number): Sphere3D {
|
||||
const { center, radius } = invariantBoundingSphere
|
||||
boundaryHelper.reset(0)
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.boundaryStep(v, radius)
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep()
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.extendStep(v, radius)
|
||||
const boundaryHelper = getHelper(transformCount)
|
||||
boundaryHelper.reset()
|
||||
|
||||
const { center, radius, extrema } = invariantBoundingSphere
|
||||
|
||||
if (extrema) {
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
for (const e of extrema) {
|
||||
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
|
||||
boundaryHelper.includePosition(v)
|
||||
}
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
for (const e of extrema) {
|
||||
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
|
||||
boundaryHelper.radiusPosition(v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.includePositionRadius(v, radius)
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.radiusPositionRadius(v, radius)
|
||||
}
|
||||
}
|
||||
|
||||
return boundaryHelper.getSphere()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -16,7 +16,6 @@ import { ValueCell } from '../mol-util';
|
||||
import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
|
||||
import { GraphicsRenderVariant } from './webgl/render-item';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { deepClone } from '../mol-util/object';
|
||||
|
||||
export interface RendererStats {
|
||||
programCount: number
|
||||
@@ -55,22 +54,63 @@ export const RendererParams = {
|
||||
interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
|
||||
interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
|
||||
|
||||
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
|
||||
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
|
||||
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
|
||||
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
|
||||
|
||||
style: PD.MappedStatic('matte', {
|
||||
custom: PD.Group({
|
||||
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
}, { isExpanded: true }),
|
||||
flat: PD.Group({}),
|
||||
matte: PD.Group({}),
|
||||
glossy: PD.Group({}),
|
||||
metallic: PD.Group({}),
|
||||
plastic: PD.Group({}),
|
||||
}, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
|
||||
}
|
||||
export type RendererProps = PD.Values<typeof RendererParams>
|
||||
|
||||
function getStyle(props: RendererProps['style']) {
|
||||
switch (props.name) {
|
||||
case 'custom':
|
||||
return props.params
|
||||
case 'flat':
|
||||
return {
|
||||
lightIntensity: 0, ambientIntensity: 1,
|
||||
metalness: 0, roughness: 0.4, reflectivity: 0.5
|
||||
}
|
||||
case 'matte':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0, roughness: 1, reflectivity: 0.5
|
||||
}
|
||||
case 'glossy':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0, roughness: 0.4, reflectivity: 0.5
|
||||
}
|
||||
case 'metallic':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0.4, roughness: 0.6, reflectivity: 0.5
|
||||
}
|
||||
case 'plastic':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0, roughness: 0.2, reflectivity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Renderer {
|
||||
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
|
||||
const { gl, state, stats } = ctx
|
||||
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
|
||||
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props)
|
||||
const style = getStyle(p.style)
|
||||
|
||||
const viewport = Viewport()
|
||||
const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor)
|
||||
@@ -112,12 +152,12 @@ namespace Renderer {
|
||||
uTransparentBackground: ValueCell.create(0),
|
||||
|
||||
// the following are general 'material' uniforms
|
||||
uLightIntensity: ValueCell.create(p.lightIntensity),
|
||||
uAmbientIntensity: ValueCell.create(p.ambientIntensity),
|
||||
uLightIntensity: ValueCell.create(style.lightIntensity),
|
||||
uAmbientIntensity: ValueCell.create(style.ambientIntensity),
|
||||
|
||||
uMetalness: ValueCell.create(p.metalness),
|
||||
uRoughness: ValueCell.create(p.roughness),
|
||||
uReflectivity: ValueCell.create(p.reflectivity),
|
||||
uMetalness: ValueCell.create(style.metalness),
|
||||
uRoughness: ValueCell.create(style.roughness),
|
||||
uReflectivity: ValueCell.create(style.reflectivity),
|
||||
|
||||
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
|
||||
|
||||
@@ -276,28 +316,6 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor))
|
||||
}
|
||||
|
||||
if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) {
|
||||
p.lightIntensity = props.lightIntensity
|
||||
ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity)
|
||||
}
|
||||
if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
|
||||
p.ambientIntensity = props.ambientIntensity
|
||||
ValueCell.update(globalUniforms.uAmbientIntensity, p.ambientIntensity)
|
||||
}
|
||||
|
||||
if (props.metalness !== undefined && props.metalness !== p.metalness) {
|
||||
p.metalness = props.metalness
|
||||
ValueCell.update(globalUniforms.uMetalness, p.metalness)
|
||||
}
|
||||
if (props.roughness !== undefined && props.roughness !== p.roughness) {
|
||||
p.roughness = props.roughness
|
||||
ValueCell.update(globalUniforms.uRoughness, p.roughness)
|
||||
}
|
||||
if (props.reflectivity !== undefined && props.reflectivity !== p.reflectivity) {
|
||||
p.reflectivity = props.reflectivity
|
||||
ValueCell.update(globalUniforms.uReflectivity, p.reflectivity)
|
||||
}
|
||||
|
||||
if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
|
||||
p.highlightColor = props.highlightColor
|
||||
ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor))
|
||||
@@ -306,6 +324,16 @@ namespace Renderer {
|
||||
p.selectColor = props.selectColor
|
||||
ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor))
|
||||
}
|
||||
|
||||
if (props.style !== undefined) {
|
||||
p.style = props.style
|
||||
Object.assign(style, getStyle(props.style))
|
||||
ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity)
|
||||
ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity)
|
||||
ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness)
|
||||
ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness)
|
||||
ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity)
|
||||
}
|
||||
},
|
||||
setViewport: (x: number, y: number, width: number, height: number) => {
|
||||
gl.viewport(x, y, width, height)
|
||||
|
||||
@@ -11,32 +11,36 @@ import { RenderableValues, BaseValues } from './renderable/schema';
|
||||
import { GraphicsRenderObject, createRenderable } from './render-object';
|
||||
import { Object3D } from './object3d';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { Vec3 } from '../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
import { CommitQueue } from './commit-queue';
|
||||
import { now } from '../mol-util/now';
|
||||
import { arraySetRemove } from '../mol-util/array';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
import { hash1 } from '../mol-data/util';
|
||||
|
||||
const boundaryHelper = new BoundaryHelper();
|
||||
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
|
||||
boundaryHelper.reset(0.1);
|
||||
const boundaryHelper = new BoundaryHelper('98')
|
||||
|
||||
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D {
|
||||
boundaryHelper.reset();
|
||||
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
if (onlyVisible && !renderables[i].state.visible) continue;
|
||||
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
boundaryHelper.boundaryStep(boundingSphere.center, boundingSphere.radius);
|
||||
|
||||
boundaryHelper.includeSphere(boundingSphere);
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep();
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
if (onlyVisible && !renderables[i].state.visible) continue;
|
||||
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
boundaryHelper.extendStep(boundingSphere.center, boundingSphere.radius);
|
||||
|
||||
boundaryHelper.radiusSphere(boundingSphere);
|
||||
}
|
||||
|
||||
Vec3.copy(boundingSphere.center, boundaryHelper.center);
|
||||
boundingSphere.radius = boundaryHelper.radius;
|
||||
|
||||
return boundingSphere;
|
||||
return boundaryHelper.getSphere(boundingSphere);
|
||||
}
|
||||
|
||||
function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Renderable<RenderableValues & BaseValues>) {
|
||||
@@ -58,7 +62,10 @@ interface Scene extends Object3D {
|
||||
readonly count: number
|
||||
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
|
||||
readonly boundingSphere: Sphere3D
|
||||
readonly boundingSphereVisible: Sphere3D
|
||||
|
||||
/** Returns `true` if some visibility has changed, `false` otherwise. */
|
||||
syncVisibility: () => boolean
|
||||
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean, isRemoving?: boolean) => void
|
||||
add: (o: GraphicsRenderObject) => void // Renderable<any>
|
||||
remove: (o: GraphicsRenderObject) => void
|
||||
@@ -73,9 +80,11 @@ namespace Scene {
|
||||
export function create(ctx: WebGLContext): Scene {
|
||||
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
|
||||
const renderables: Renderable<RenderableValues & BaseValues>[] = []
|
||||
const boundingSphere = Sphere3D.zero()
|
||||
const boundingSphere = Sphere3D()
|
||||
const boundingSphereVisible = Sphere3D()
|
||||
|
||||
let boundingSphereDirty = true
|
||||
let boundingSphereVisibleDirty = true
|
||||
|
||||
const object3d = Object3D.create()
|
||||
|
||||
@@ -85,6 +94,7 @@ namespace Scene {
|
||||
renderables.push(renderable)
|
||||
renderableMap.set(o, renderable)
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
return renderable;
|
||||
} else {
|
||||
console.warn(`RenderObject with id '${o.id}' already present`)
|
||||
@@ -99,6 +109,7 @@ namespace Scene {
|
||||
arraySetRemove(renderables, renderable);
|
||||
renderableMap.delete(o)
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,17 +137,37 @@ namespace Scene {
|
||||
return true;
|
||||
}
|
||||
|
||||
// const toAdd: GraphicsRenderObject[] = []
|
||||
// const toRemove: GraphicsRenderObject[] = []
|
||||
const commitQueue = new CommitQueue();
|
||||
|
||||
let visibleHash = -1
|
||||
function computeVisibleHash() {
|
||||
let hash = 23
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
if (!renderables[i].state.visible) continue;
|
||||
hash = (31 * hash + renderables[i].id) | 0;
|
||||
}
|
||||
hash = hash1(hash);
|
||||
if (hash === -1) hash = 0;
|
||||
return hash
|
||||
}
|
||||
|
||||
function syncVisibility() {
|
||||
const newVisibleHash = computeVisibleHash()
|
||||
if (newVisibleHash !== visibleHash) {
|
||||
boundingSphereVisibleDirty = true
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get view () { return object3d.view },
|
||||
get position () { return object3d.position },
|
||||
get direction () { return object3d.direction },
|
||||
get up () { return object3d.up },
|
||||
// get isCommiting () { return commitQueue.length > 0 },
|
||||
|
||||
syncVisibility,
|
||||
update(objects, keepBoundingSphere, isRemoving) {
|
||||
Object3D.update(object3d)
|
||||
if (objects) {
|
||||
@@ -150,7 +181,12 @@ namespace Scene {
|
||||
renderables[i].update()
|
||||
}
|
||||
}
|
||||
if (!keepBoundingSphere) boundingSphereDirty = true
|
||||
if (!keepBoundingSphere) {
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
} else {
|
||||
syncVisibility()
|
||||
}
|
||||
},
|
||||
add: (o: GraphicsRenderObject) => commitQueue.add(o),
|
||||
remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
|
||||
@@ -166,6 +202,7 @@ namespace Scene {
|
||||
renderables.length = 0
|
||||
renderableMap.clear()
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
},
|
||||
forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
|
||||
renderableMap.forEach(callbackFn)
|
||||
@@ -175,9 +212,19 @@ namespace Scene {
|
||||
},
|
||||
renderables,
|
||||
get boundingSphere() {
|
||||
if (boundingSphereDirty) calculateBoundingSphere(renderables, boundingSphere)
|
||||
boundingSphereDirty = false
|
||||
if (boundingSphereDirty) {
|
||||
calculateBoundingSphere(renderables, boundingSphere, false)
|
||||
boundingSphereDirty = false
|
||||
}
|
||||
return boundingSphere
|
||||
},
|
||||
get boundingSphereVisible() {
|
||||
if (boundingSphereVisibleDirty) {
|
||||
calculateBoundingSphere(renderables, boundingSphereVisible, true)
|
||||
boundingSphereVisibleDirty = false
|
||||
visibleHash = computeVisibleHash()
|
||||
}
|
||||
return boundingSphereVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ShaderExtensions {
|
||||
|
||||
export interface ShaderCode {
|
||||
readonly id: number
|
||||
readonly name: string
|
||||
readonly vert: string
|
||||
readonly frag: string
|
||||
readonly extensions: ShaderExtensions
|
||||
@@ -37,7 +38,6 @@ import assign_color_varying from './shader/chunks/assign-color-varying.glsl'
|
||||
import assign_group from './shader/chunks/assign-group.glsl'
|
||||
import assign_marker_varying from './shader/chunks/assign-marker-varying.glsl'
|
||||
import assign_material_color from './shader/chunks/assign-material-color.glsl'
|
||||
import assign_normal from './shader/chunks/assign-normal.glsl'
|
||||
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'
|
||||
@@ -63,7 +63,6 @@ const ShaderChunks: { [k: string]: string } = {
|
||||
assign_group,
|
||||
assign_marker_varying,
|
||||
assign_material_color,
|
||||
assign_normal,
|
||||
assign_position,
|
||||
assign_size,
|
||||
check_picking_alpha,
|
||||
@@ -99,33 +98,33 @@ function addIncludes(text: string) {
|
||||
.replace(reMultipleLinebreaks, '\n')
|
||||
}
|
||||
|
||||
export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
|
||||
return { id: shaderCodeId(), vert: addIncludes(vert), frag: addIncludes(frag), extensions }
|
||||
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
|
||||
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions }
|
||||
}
|
||||
|
||||
import points_vert from './shader/points.vert'
|
||||
import points_frag from './shader/points.frag'
|
||||
export const PointsShaderCode = ShaderCode(points_vert, points_frag)
|
||||
export const PointsShaderCode = ShaderCode('points', points_vert, points_frag)
|
||||
|
||||
import spheres_vert from './shader/spheres.vert'
|
||||
import spheres_frag from './shader/spheres.frag'
|
||||
export const SpheresShaderCode = ShaderCode(spheres_vert, spheres_frag, { fragDepth: true })
|
||||
export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: true })
|
||||
|
||||
import text_vert from './shader/text.vert'
|
||||
import text_frag from './shader/text.frag'
|
||||
export const TextShaderCode = ShaderCode(text_vert, text_frag, { standardDerivatives: true })
|
||||
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: true })
|
||||
|
||||
import lines_vert from './shader/lines.vert'
|
||||
import lines_frag from './shader/lines.frag'
|
||||
export const LinesShaderCode = ShaderCode(lines_vert, lines_frag)
|
||||
export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag)
|
||||
|
||||
import mesh_vert from './shader/mesh.vert'
|
||||
import mesh_frag from './shader/mesh.frag'
|
||||
export const MeshShaderCode = ShaderCode(mesh_vert, mesh_frag, { standardDerivatives: true })
|
||||
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: true })
|
||||
|
||||
import direct_volume_vert from './shader/direct-volume.vert'
|
||||
import direct_volume_frag from './shader/direct-volume.frag'
|
||||
export const DirectVolumeShaderCode = ShaderCode(direct_volume_vert, direct_volume_frag, { fragDepth: true })
|
||||
export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: true })
|
||||
|
||||
//
|
||||
|
||||
@@ -235,6 +234,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
|
||||
const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag
|
||||
return {
|
||||
id: shaderCodeId(),
|
||||
name: shaders.name,
|
||||
vert: `${vertPrefix}${header}${shaders.vert}`,
|
||||
frag: `${fragPrefix}${header}${frag}`,
|
||||
extensions: shaders.extensions
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export default `
|
||||
// inputs
|
||||
// - vViewPosition (if dFlatShaded)
|
||||
// - vNormal (if NOT dFlatShaded)
|
||||
|
||||
// outputs
|
||||
// - normal
|
||||
|
||||
// surface normal
|
||||
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
|
||||
vec3 fdx = dFdx(vViewPosition);
|
||||
vec3 fdy = dFdy(vViewPosition);
|
||||
vec3 normal = -normalize(cross(fdx, fdy));
|
||||
#else
|
||||
vec3 normal = -normalize(vNormal);
|
||||
#ifdef dDoubleSided
|
||||
normal = normal * (float(gl_FrontFacing) * 2.0 - 1.0);
|
||||
#endif
|
||||
#endif
|
||||
`
|
||||
@@ -1,5 +1,3 @@
|
||||
export default `
|
||||
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
|
||||
varying vec3 vNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
`
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,7 +15,22 @@ precision highp int;
|
||||
#include normal_frag_params
|
||||
|
||||
void main() {
|
||||
interior = !gl_FrontFacing; // TODO take dFlipSided into account
|
||||
// Workaround for buggy gl_FrontFacing (e.g. on some integrated Intel GPUs)
|
||||
#if defined(enabledStandardDerivatives)
|
||||
vec3 fdx = dFdx(vViewPosition);
|
||||
vec3 fdy = dFdy(vViewPosition);
|
||||
vec3 faceNormal = normalize(cross(fdx,fdy));
|
||||
bool frontFacing = dot(vNormal, faceNormal) > 0.0;
|
||||
#else
|
||||
bool frontFacing = dot(vNormal, vViewPosition) < 0.0;
|
||||
#endif
|
||||
|
||||
#if defined(dFlipSided)
|
||||
interior = frontFacing;
|
||||
#else
|
||||
interior = !frontFacing;
|
||||
#endif
|
||||
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
@@ -27,7 +42,14 @@ void main() {
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#include assign_normal
|
||||
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
|
||||
vec3 normal = -faceNormal;
|
||||
#else
|
||||
vec3 normal = -normalize(vNormal);
|
||||
#ifdef dDoubleSided
|
||||
normal = normal * (float(frontFacing) * 2.0 - 1.0);
|
||||
#endif
|
||||
#endif
|
||||
#include apply_light_color
|
||||
#endif
|
||||
|
||||
|
||||
@@ -23,14 +23,12 @@ attribute mat4 aTransform;
|
||||
attribute float aInstance;
|
||||
attribute float aGroup;
|
||||
|
||||
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives) || defined(dIgnoreLight)
|
||||
#ifdef dGeoTexture
|
||||
uniform sampler2D tNormal;
|
||||
#else
|
||||
attribute vec3 aNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
#ifdef dGeoTexture
|
||||
uniform sampler2D tNormal;
|
||||
#else
|
||||
attribute vec3 aNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
|
||||
void main(){
|
||||
#include assign_group
|
||||
@@ -38,18 +36,16 @@ void main(){
|
||||
#include assign_marker_varying
|
||||
#include assign_position
|
||||
|
||||
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives) || defined(dIgnoreLight)
|
||||
#ifdef dGeoTexture
|
||||
vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 normal = aNormal;
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(modelView)));
|
||||
vec3 transformedNormal = normalize(normalMatrix * normalize(normal));
|
||||
#if defined(dFlipSided) && !defined(dDoubleSided) // TODO checking dDoubleSided should not be required, ASR
|
||||
transformedNormal = -transformedNormal;
|
||||
#endif
|
||||
vNormal = transformedNormal;
|
||||
#ifdef dGeoTexture
|
||||
vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 normal = aNormal;
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(modelView)));
|
||||
vec3 transformedNormal = normalize(normalMatrix * normalize(normal));
|
||||
#if defined(dFlipSided) && !defined(dDoubleSided) // TODO checking dDoubleSided should not be required, ASR
|
||||
transformedNormal = -transformedNormal;
|
||||
#endif
|
||||
vNormal = transformedNormal;
|
||||
}
|
||||
`
|
||||
@@ -38,10 +38,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
|
||||
const spec = schema[k]
|
||||
if (spec.type === 'attribute') {
|
||||
const loc = gl.getAttribLocation(program, k)
|
||||
// unused attributes will result in a `-1` location which is usually fine
|
||||
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
|
||||
locations[k] = loc
|
||||
} else if (spec.type === 'uniform' || spec.type === 'texture') {
|
||||
const loc = gl.getUniformLocation(program, k)
|
||||
// unused uniforms will result in a `null` location which is usually fine
|
||||
// if (loc === null) console.info(`Could not get uniform location for '${k}'`)
|
||||
locations[k] = loc as number
|
||||
}
|
||||
@@ -146,8 +148,8 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
const vertShader = getShader('vert', shaderCode.vert)
|
||||
const fragShader = getShader('frag', shaderCode.frag)
|
||||
|
||||
let locations: Locations // = getLocations(gl, program, schema)
|
||||
let uniformSetters: UniformSetters // = getUniformSetters(schema)
|
||||
let locations: Locations
|
||||
let uniformSetters: UniformSetters
|
||||
|
||||
function init() {
|
||||
vertShader.attach(program)
|
||||
@@ -186,9 +188,9 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
}
|
||||
}
|
||||
},
|
||||
bindAttributes: (attribueBuffers: AttributeBuffers) => {
|
||||
for (let i = 0, il = attribueBuffers.length; i < il; ++i) {
|
||||
const [k, buffer] = attribueBuffers[i]
|
||||
bindAttributes: (attributeBuffers: AttributeBuffers) => {
|
||||
for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
|
||||
const [k, buffer] = attributeBuffers[i]
|
||||
const l = locations[k]
|
||||
if (l !== -1) buffer.bind(l)
|
||||
}
|
||||
|
||||
@@ -198,9 +198,6 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
try {
|
||||
checkError(ctx.gl)
|
||||
} catch (e) {
|
||||
// console.log('shaderCode', shaderCode)
|
||||
// console.log('schema', schema)
|
||||
// console.log('attributeBuffers', attributeBuffers)
|
||||
throw new Error(`Error rendering item id ${id}: '${e}'`)
|
||||
}
|
||||
}
|
||||
@@ -246,10 +243,10 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
const value = attributeValues[k]
|
||||
if (value.ref.version !== versions[k]) {
|
||||
if (buffer.length >= value.ref.value.length) {
|
||||
// console.log('attribute array large enough to update', k, value.ref.id, value.ref.version)
|
||||
// console.log('attribute array large enough to update', buffer.id, k, value.ref.id, value.ref.version)
|
||||
buffer.updateData(value.ref.value)
|
||||
} else {
|
||||
// console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
|
||||
// console.log('attribute array too small, need to create new attribute', buffer.id, k, value.ref.id, value.ref.version)
|
||||
buffer.destroy()
|
||||
const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
|
||||
attributeBuffers[i][1] = resources.attribute(value.ref.value, itemSize, divisor)
|
||||
@@ -276,7 +273,8 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
// console.log('program/defines or buffers changed, update vaos')
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
const vertexArray = vertexArrays[k]
|
||||
if (vertexArray) vertexArray.update()
|
||||
if (vertexArray) vertexArray.destroy()
|
||||
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -129,7 +129,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* Defines the polymer characteristic of the entity.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
|
||||
/**
|
||||
* Additional details about this entity.
|
||||
*/
|
||||
@@ -423,7 +423,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* The monomer type for the sequence.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'peptide-like' | 'saccharide'>(str),
|
||||
/**
|
||||
* A flag to indicate a non-ribosomal entity.
|
||||
*/
|
||||
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* An identifier for the wwPDB site creating or modifying the molecule.
|
||||
*/
|
||||
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB'>(str),
|
||||
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
|
||||
/**
|
||||
* The action associated with this audit record.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -175,7 +175,7 @@ export const CCD_Schema = {
|
||||
* This data item identifies the deposition site that processed
|
||||
* this chemical component defintion.
|
||||
*/
|
||||
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'RCSB'>(str),
|
||||
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'PDBC' | 'RCSB'>(str),
|
||||
},
|
||||
/**
|
||||
* Data items in the CHEM_COMP_ATOM category record details about
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.11.
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.13.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -185,7 +185,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The fraction of the atom type present at this site.
|
||||
* The sum of the occupancies of all the atom types at this site
|
||||
* may not significantly exceed 1.0 unless it is a dummy site.
|
||||
* may not exceed 1.0 unless it is a dummy site.
|
||||
*/
|
||||
occupancy: float,
|
||||
/**
|
||||
@@ -1774,11 +1774,11 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL'>(str),
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL' | 'PDBC'>(str),
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL'>(str),
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL' | 'PDBC'>(str),
|
||||
/**
|
||||
* Code for status of chemical shift data file.
|
||||
*/
|
||||
@@ -2192,7 +2192,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* Defines the polymer characteristic of the entity.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
|
||||
/**
|
||||
* Additional details about this entity.
|
||||
*/
|
||||
@@ -2575,6 +2575,48 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
ordinal: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the pdbx_entity_instance_feature category records
|
||||
* special features of selected entity instances.
|
||||
*/
|
||||
pdbx_entity_instance_feature: {
|
||||
/**
|
||||
* Special structural details about this entity instance.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
* A feature type associated with entity instance.
|
||||
*/
|
||||
feature_type: Aliased<'SUBJECT OF INVESTIGATION' | 'NO FUNCTIONAL ROLE' | 'OTHER'>(str),
|
||||
/**
|
||||
* Author instance identifier (formerly PDB Chain ID)
|
||||
*/
|
||||
auth_asym_id: str,
|
||||
/**
|
||||
* Instance identifier for this entity.
|
||||
*/
|
||||
asym_id: str,
|
||||
/**
|
||||
* Author provided residue number.
|
||||
*/
|
||||
auth_seq_num: str,
|
||||
/**
|
||||
* Position in the sequence.
|
||||
*/
|
||||
seq_num: int,
|
||||
/**
|
||||
* Chemical component identifier
|
||||
*/
|
||||
comp_id: str,
|
||||
/**
|
||||
* The author provided chemical component identifier
|
||||
*/
|
||||
auth_comp_id: str,
|
||||
/**
|
||||
* An ordinal index for this category
|
||||
*/
|
||||
ordinal: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the IHM_STARTING_MODEL_DETAILS category records the
|
||||
* details about structural models used as starting inputs in
|
||||
@@ -4585,6 +4627,21 @@ export const mmCIF_Schema = {
|
||||
* PDBX_ENTITY_BRANCH_LIST category.
|
||||
*/
|
||||
num: int,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_asym_id: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_seq_num: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_mon_id: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.pdbx_auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import TextEncoder from './cif/encoder/text'
|
||||
import BinaryEncoder, { EncodingProvider } from './cif/encoder/binary'
|
||||
import BinaryEncoder, { BinaryEncodingProvider } from './cif/encoder/binary'
|
||||
import * as _Encoder from './cif/encoder'
|
||||
import { ArrayEncoding, ArrayEncoder } from '../common/binary-cif';
|
||||
import { CifFrame } from '../reader/cif';
|
||||
@@ -20,7 +20,7 @@ export namespace CifWriter {
|
||||
export interface EncoderParams {
|
||||
binary?: boolean,
|
||||
encoderName?: string,
|
||||
binaryEncodingPovider?: EncodingProvider,
|
||||
binaryEncodingPovider?: BinaryEncodingProvider,
|
||||
binaryAutoClassifyEncoding?: boolean
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ export namespace CifWriter {
|
||||
return binary ? new BinaryEncoder(encoderName, params ? params.binaryEncodingPovider : void 0, params ? !!params.binaryAutoClassifyEncoding : false) : new TextEncoder();
|
||||
}
|
||||
|
||||
export function fields<K = number, D = any>() {
|
||||
return Field.build<K, D>();
|
||||
export function fields<K = number, D = any, N extends string = string>() {
|
||||
return Field.build<K, D, N>();
|
||||
}
|
||||
|
||||
import E = Encoding
|
||||
@@ -44,7 +44,7 @@ export namespace CifWriter {
|
||||
return { fields, source: [source] };
|
||||
}
|
||||
|
||||
export function createEncodingProviderFromCifFrame(frame: CifFrame): EncodingProvider {
|
||||
export function createEncodingProviderFromCifFrame(frame: CifFrame): BinaryEncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
const cat = frame.categories[c];
|
||||
@@ -55,7 +55,7 @@ export namespace CifWriter {
|
||||
}
|
||||
};
|
||||
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): BinaryEncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Column, Table, Database, DatabaseCollection } from '../../../mol-data/d
|
||||
import { Tensor } from '../../../mol-math/linear-algebra'
|
||||
import EncoderBase from '../encoder'
|
||||
import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
|
||||
import { BinaryEncodingProvider } from './encoder/binary';
|
||||
|
||||
// TODO: support for "coordinate fields", make "coordinate precision" a parameter of the encoder
|
||||
// TODO: automatically detect "precision" of floating point arrays.
|
||||
@@ -72,25 +73,32 @@ export namespace Field {
|
||||
return int(name, (e, d, i) => i + 1, { typedArray: Int32Array, encoder: ArrayEncoding.by(ArrayEncoding.delta).and(ArrayEncoding.runLength).and(ArrayEncoding.integerPacking) })
|
||||
}
|
||||
|
||||
export class Builder<K = number, D = any> {
|
||||
export class Builder<K = number, D = any, N extends string = string> {
|
||||
private fields: Field<K, D>[] = [];
|
||||
|
||||
index(name: string) {
|
||||
index(name: N) {
|
||||
this.fields.push(Field.index(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
str(name: string, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
|
||||
str(name: N, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
|
||||
this.fields.push(Field.str(name, value, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
int(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
int(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
this.fields.push(Field.int(name, value, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
float(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
|
||||
vec(name: N, values: ((k: K, d: D, index: number) => number)[], params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
this.fields.push(Field.int(`${name}[${i + 1}]`, values[i], params));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
float(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
|
||||
this.fields.push(Field.float(name, value, params));
|
||||
return this;
|
||||
}
|
||||
@@ -103,8 +111,8 @@ export namespace Field {
|
||||
getFields() { return this.fields; }
|
||||
}
|
||||
|
||||
export function build<K = number, D = any>() {
|
||||
return new Builder<K, D>();
|
||||
export function build<K = number, D = any, N extends string = string>() {
|
||||
return new Builder<K, D, N>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +207,7 @@ export namespace Category {
|
||||
getFormat(cat, field) { return void 0; }
|
||||
}
|
||||
|
||||
export function ofTable(table: Table<Table.Schema>, indices?: ArrayLike<number>): Category.Instance {
|
||||
export function ofTable(table: Table, indices?: ArrayLike<number>): Category.Instance {
|
||||
if (indices) {
|
||||
return {
|
||||
fields: cifFieldsFromTableSchema(table._schema),
|
||||
@@ -215,14 +223,21 @@ export namespace Category {
|
||||
|
||||
export interface Encoder<T = string | Uint8Array> extends EncoderBase {
|
||||
setFilter(filter?: Category.Filter): void,
|
||||
isCategoryIncluded(name: string): boolean,
|
||||
setFormatter(formatter?: Category.Formatter): void,
|
||||
|
||||
startDataBlock(header: string): void,
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx): void,
|
||||
getData(): T
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions): void,
|
||||
getData(): T,
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined;
|
||||
}
|
||||
|
||||
export namespace Encoder {
|
||||
export interface WriteCategoryOptions {
|
||||
ignoreFilter?: boolean
|
||||
}
|
||||
|
||||
export function writeDatabase(encoder: Encoder, name: string, database: Database<Database.Schema>) {
|
||||
encoder.startDataBlock(name);
|
||||
for (const table of database._tableNames) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { getIncludedFields, getCategoryInstanceData, CategoryInstanceData } from
|
||||
import { classifyIntArray, classifyFloatArray } from '../../../common/binary-cif/classifier';
|
||||
import { ArrayCtor } from '../../../../mol-util/type-helpers';
|
||||
|
||||
export interface EncodingProvider {
|
||||
export interface BinaryEncodingProvider {
|
||||
get(category: string, field: string): ArrayEncoder | undefined;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,16 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
this.filter = filter || Category.DefaultFilter;
|
||||
}
|
||||
|
||||
isCategoryIncluded(name: string) {
|
||||
return this.filter.includeCategory(name);
|
||||
}
|
||||
|
||||
setFormatter(formatter?: Category.Formatter) {
|
||||
this.formatter = formatter || Category.DefaultFormatter;
|
||||
}
|
||||
@@ -43,7 +49,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
});
|
||||
}
|
||||
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
|
||||
if (!this.data) {
|
||||
throw new Error('The writer contents have already been encoded, no more writing.');
|
||||
}
|
||||
@@ -52,7 +58,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
throw new Error('No data block created.');
|
||||
}
|
||||
|
||||
if (!this.filter.includeCategory(category.name)) return;
|
||||
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
|
||||
|
||||
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
|
||||
if (!rowCount) return;
|
||||
@@ -64,7 +70,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
if (!this.filter.includeField(category.name, f.name)) continue;
|
||||
|
||||
const format = this.formatter.getFormat(category.name, f.name);
|
||||
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.encodingProvider, this.autoClassify));
|
||||
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.binaryEncodingProvider, this.autoClassify));
|
||||
}
|
||||
// no columns included.
|
||||
if (!cat.columns.length) return;
|
||||
@@ -88,7 +94,8 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
return this.encodedData;
|
||||
}
|
||||
|
||||
constructor(encoder: string, private encodingProvider: EncodingProvider | undefined, private autoClassify: boolean) {
|
||||
constructor(encoder: string, encodingProvider: BinaryEncodingProvider | undefined, private autoClassify: boolean) {
|
||||
this.binaryEncodingProvider = encodingProvider;
|
||||
this.data = {
|
||||
encoder,
|
||||
version: VERSION,
|
||||
@@ -110,7 +117,7 @@ function getDefaultEncoder(type: Field.Type): ArrayEncoder {
|
||||
return ArrayEncoder.by(E.byteArray);
|
||||
}
|
||||
|
||||
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: EncodingProvider | undefined) {
|
||||
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: BinaryEncodingProvider | undefined) {
|
||||
if (format && format.encoder) {
|
||||
return format.encoder;
|
||||
} else if (field.defaultFormat && field.defaultFormat.encoder) {
|
||||
@@ -129,7 +136,7 @@ function classify(type: Field.Type, data: ArrayLike<any>) {
|
||||
}
|
||||
|
||||
function encodeField(categoryName: string, field: Field, data: CategoryInstanceData['source'], totalCount: number,
|
||||
format: Field.Format | undefined, encoderProvider: EncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
|
||||
format: Field.Format | undefined, encoderProvider: BinaryEncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
|
||||
|
||||
const { array, allPresent, mask } = getFieldData(field, getArrayCtor(field, format), totalCount, data);
|
||||
|
||||
|
||||
@@ -19,10 +19,16 @@ export default class TextEncoder implements Encoder<string> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
binaryEncodingProvider = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
this.filter = filter || Category.DefaultFilter;
|
||||
}
|
||||
|
||||
isCategoryIncluded(name: string) {
|
||||
return this.filter.includeCategory(name);
|
||||
}
|
||||
|
||||
setFormatter(formatter?: Category.Formatter) {
|
||||
this.formatter = formatter || Category.DefaultFormatter;
|
||||
}
|
||||
@@ -32,7 +38,7 @@ export default class TextEncoder implements Encoder<string> {
|
||||
StringBuilder.write(this.builder, `data_${(header || '').replace(/[ \n\t]/g, '').toUpperCase()}\n#\n`);
|
||||
}
|
||||
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
|
||||
if (this.encoded) {
|
||||
throw new Error('The writer contents have already been encoded, no more writing.');
|
||||
}
|
||||
@@ -41,7 +47,7 @@ export default class TextEncoder implements Encoder<string> {
|
||||
throw new Error('No data block created.');
|
||||
}
|
||||
|
||||
if (!this.filter.includeCategory(category.name)) return;
|
||||
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
|
||||
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
|
||||
if (!rowCount) return;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -7,6 +7,7 @@
|
||||
import { GridLookup3D } from '../../geometry';
|
||||
import { sortArray } from '../../../mol-data/util';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { getBoundary } from '../boundary';
|
||||
|
||||
const xs = [0, 0, 1];
|
||||
const ys = [0, 1, 0];
|
||||
@@ -15,7 +16,9 @@ const rs = [0, 0.5, 1/3];
|
||||
|
||||
describe('GridLookup3d', () => {
|
||||
it('basic', () => {
|
||||
const grid = GridLookup3D({ x: xs, y: ys, z: zs, indices: OrderedSet.ofBounds(0, 3) });
|
||||
const position = { x: xs, y: ys, z: zs, indices: OrderedSet.ofBounds(0, 3) }
|
||||
const boundary = getBoundary(position)
|
||||
const grid = GridLookup3D(position, boundary);
|
||||
|
||||
let r = grid.find(0, 0, 0, 0);
|
||||
expect(r.count).toBe(1);
|
||||
@@ -27,7 +30,9 @@ describe('GridLookup3d', () => {
|
||||
});
|
||||
|
||||
it('radius', () => {
|
||||
const grid = GridLookup3D({ x: xs, y: ys, z: zs, radius: [0, 0.5, 1 / 3], indices: OrderedSet.ofBounds(0, 3) });
|
||||
const position = { x: xs, y: ys, z: zs, radius: [0, 0.5, 1 / 3], indices: OrderedSet.ofBounds(0, 3) }
|
||||
const boundary = getBoundary(position)
|
||||
const grid = GridLookup3D(position, boundary);
|
||||
|
||||
let r = grid.find(0, 0, 0, 0);
|
||||
expect(r.count).toBe(1);
|
||||
@@ -39,7 +44,9 @@ describe('GridLookup3d', () => {
|
||||
});
|
||||
|
||||
it('indexed', () => {
|
||||
const grid = GridLookup3D({ x: xs, y: ys, z: zs, indices: OrderedSet.ofSingleton(1), radius: rs });
|
||||
const position = { x: xs, y: ys, z: zs, indices: OrderedSet.ofSingleton(1), radius: rs }
|
||||
const boundary = getBoundary(position)
|
||||
const grid = GridLookup3D(position, boundary);
|
||||
|
||||
let r = grid.find(0, 0, 0, 0);
|
||||
expect(r.count).toBe(0);
|
||||
|
||||
36
src/mol-math/geometry/_spec/spacegroup.spec.ts
Normal file
36
src/mol-math/geometry/_spec/spacegroup.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Spacegroup, SpacegroupCell } from '../spacegroup/construction';
|
||||
import { Vec3 } from '../../linear-algebra';
|
||||
|
||||
function getSpacegroup(name: string) {
|
||||
const size = Vec3.create(1, 1, 1)
|
||||
const anglesInRadians = Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2)
|
||||
const cell = SpacegroupCell.create(name, size, anglesInRadians)
|
||||
return Spacegroup.create(cell)
|
||||
}
|
||||
|
||||
function checkOperatorsXyz(name: string, expected: string[]) {
|
||||
const spacegroup = getSpacegroup(name)
|
||||
for (let i = 0, il = spacegroup.operators.length; i < il; ++i) {
|
||||
const op = spacegroup.operators[i]
|
||||
const actual = Spacegroup.getOperatorXyz(op)
|
||||
expect(actual).toBe(expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
describe('Spacegroup', () => {
|
||||
it('operators xyz', () => {
|
||||
checkOperatorsXyz('P 1', ['X,Y,Z'])
|
||||
checkOperatorsXyz('P -1', ['X,Y,Z', '-X,-Y,-Z'])
|
||||
checkOperatorsXyz('P 1 21 1', ['X,Y,Z', '-X,1/2+Y,-Z'])
|
||||
checkOperatorsXyz('P 1 21/m 1', ['X,Y,Z', '-X,1/2+Y,-Z', '-X,-Y,-Z', 'X,1/2-Y,Z'])
|
||||
checkOperatorsXyz('P 41', ['X,Y,Z', '-X,-Y,1/2+Z', '-Y,X,1/4+Z', 'Y,-X,3/4+Z'])
|
||||
checkOperatorsXyz('P 41 21 2', ['X,Y,Z', '-X,-Y,1/2+Z', '1/2-Y,1/2+X,1/4+Z', '1/2+Y,1/2-X,3/4+Z', '1/2-X,1/2+Y,1/4-Z', '1/2+X,1/2-Y,3/4-Z', 'Y,X,-Z', '-Y,-X,1/2-Z'])
|
||||
checkOperatorsXyz('P 3', ['X,Y,Z', '-Y,X-Y,Z', 'Y-X,-X,Z'])
|
||||
});
|
||||
})
|
||||
@@ -1,124 +1,165 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../mol-math/linear-algebra/3d';
|
||||
import { Vec3 } from '../linear-algebra/3d';
|
||||
import { CentroidHelper } from './centroid-helper';
|
||||
import { Sphere3D } from '../geometry';
|
||||
import { Box3D } from './primitives/box3d';
|
||||
import { Sphere3D } from './primitives/sphere3d';
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* 1. .reset(tolerance); tolerance plays part in the "extend" step
|
||||
* 2. for each point/sphere call boundaryStep()
|
||||
* 3. .finishBoundaryStep
|
||||
* 4. for each point/sphere call extendStep
|
||||
* 5. use .center/.radius or call getSphere/getBox
|
||||
*/
|
||||
// implementing http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
|
||||
|
||||
export class BoundaryHelper {
|
||||
private count = 0;
|
||||
private extremes = [Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero()];
|
||||
private u = Vec3.zero();
|
||||
private v = Vec3.zero();
|
||||
private dir: Vec3[]
|
||||
|
||||
tolerance = 0;
|
||||
center: Vec3 = Vec3.zero();
|
||||
radius = 0;
|
||||
private minDist: number[] = []
|
||||
private maxDist: number[] = []
|
||||
private extrema: Vec3[] = []
|
||||
centroidHelper = new CentroidHelper()
|
||||
|
||||
reset(tolerance: number) {
|
||||
Vec3.set(this.center, 0, 0, 0);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const e = i % 2 === 0 ? Number.MAX_VALUE : -Number.MAX_VALUE;
|
||||
Vec3.set(this.extremes[i], e, e, e);
|
||||
private computeExtrema(i: number, p: Vec3) {
|
||||
const d = Vec3.dot(this.dir[i], p)
|
||||
|
||||
if (d < this.minDist[i]) {
|
||||
this.minDist[i] = d
|
||||
Vec3.copy(this.extrema[i * 2], p)
|
||||
}
|
||||
if (d > this.maxDist[i]) {
|
||||
this.maxDist[i] = d
|
||||
Vec3.copy(this.extrema[i * 2 + 1], p)
|
||||
}
|
||||
this.radius = 0;
|
||||
this.count = 0;
|
||||
this.tolerance = tolerance;
|
||||
}
|
||||
|
||||
boundaryStep(p: Vec3, r: number) {
|
||||
updateExtremeMin(0, this.extremes[0], p, r);
|
||||
updateExtremeMax(0, this.extremes[1], p, r);
|
||||
private computeSphereExtrema(i: number, center: Vec3, radius: number) {
|
||||
const d = Vec3.dot(this.dir[i], center)
|
||||
|
||||
updateExtremeMin(1, this.extremes[2], p, r);
|
||||
updateExtremeMax(1, this.extremes[3], p, r);
|
||||
|
||||
updateExtremeMin(2, this.extremes[4], p, r);
|
||||
updateExtremeMax(2, this.extremes[5], p, r);
|
||||
this.count++;
|
||||
if (d - radius < this.minDist[i]) {
|
||||
this.minDist[i] = d - radius
|
||||
Vec3.scaleAndSub(this.extrema[i * 2], center, this.dir[i], radius)
|
||||
}
|
||||
if (d + radius > this.maxDist[i]) {
|
||||
this.maxDist[i] = d + radius
|
||||
Vec3.scaleAndAdd(this.extrema[i * 2 + 1], center, this.dir[i], radius)
|
||||
}
|
||||
}
|
||||
|
||||
finishBoundaryStep() {
|
||||
if (this.count === 0) return;
|
||||
|
||||
let maxSpan = 0, mI = 0, mJ = 0;
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
for (let j = i + 1; j < 6; j++) {
|
||||
const d = Vec3.squaredDistance(this.extremes[i], this.extremes[j]);
|
||||
if (d > maxSpan) {
|
||||
maxSpan = d;
|
||||
mI = i;
|
||||
mJ = j;
|
||||
}
|
||||
includeSphere(s: Sphere3D) {
|
||||
if (Sphere3D.hasExtrema(s)) {
|
||||
for (const e of s.extrema) {
|
||||
this.includePosition(e);
|
||||
}
|
||||
} else {
|
||||
this.includePositionRadius(s.center, s.radius);
|
||||
}
|
||||
|
||||
Vec3.add(this.center, this.extremes[mI], this.extremes[mJ]);
|
||||
Vec3.scale(this.center, this.center, 0.5);
|
||||
this.radius = Vec3.distance(this.center, this.extremes[mI]);
|
||||
}
|
||||
|
||||
extendStep(p: Vec3, r: number) {
|
||||
const d = Vec3.distance(p, this.center);
|
||||
if ((1 + this.tolerance) * this.radius >= r + d) return;
|
||||
|
||||
Vec3.sub(this.u, p, this.center);
|
||||
Vec3.normalize(this.u, this.u);
|
||||
|
||||
Vec3.scale(this.v, this.u, -this.radius);
|
||||
Vec3.add(this.v, this.v, this.center);
|
||||
Vec3.scale(this.u, this.u, r + d);
|
||||
Vec3.add(this.u, this.u, this.center);
|
||||
|
||||
Vec3.add(this.center, this.u, this.v);
|
||||
Vec3.scale(this.center, this.center, 0.5);
|
||||
this.radius = 0.5 * (r + d + this.radius);
|
||||
}
|
||||
|
||||
getBox(): Box3D {
|
||||
Vec3.copy(this.u, this.extremes[0]);
|
||||
Vec3.copy(this.v, this.extremes[0]);
|
||||
|
||||
for (let i = 1; i < 6; i++) {
|
||||
Vec3.min(this.u, this.u, this.extremes[i]);
|
||||
Vec3.max(this.v, this.v, this.extremes[i]);
|
||||
includePosition(p: Vec3) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeExtrema(i, p)
|
||||
}
|
||||
|
||||
return { min: Vec3.clone(this.u), max: Vec3.clone(this.v) };
|
||||
}
|
||||
|
||||
getSphere(): Sphere3D {
|
||||
return { center: Vec3.clone(this.center), radius: this.radius };
|
||||
includePositionRadius(center: Vec3, radius: number) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeSphereExtrema(i, center, radius)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.reset(0);
|
||||
finishedIncludeStep() {
|
||||
for (let i = 0; i < this.extrema.length; i++) {
|
||||
this.centroidHelper.includeStep(this.extrema[i]);
|
||||
}
|
||||
this.centroidHelper.finishedIncludeStep();
|
||||
}
|
||||
|
||||
radiusSphere(s: Sphere3D) {
|
||||
if (Sphere3D.hasExtrema(s)) {
|
||||
for (const e of s.extrema) {
|
||||
this.radiusPosition(e)
|
||||
}
|
||||
} else {
|
||||
this.radiusPositionRadius(s.center, s.radius);
|
||||
}
|
||||
}
|
||||
|
||||
radiusPosition(p: Vec3) {
|
||||
this.centroidHelper.radiusStep(p);
|
||||
}
|
||||
|
||||
radiusPositionRadius(center: Vec3, radius: number) {
|
||||
this.centroidHelper.radiusSphereStep(center, radius);
|
||||
}
|
||||
|
||||
getSphere(sphere?: Sphere3D) {
|
||||
return Sphere3D.setExtrema(this.centroidHelper.getSphere(sphere), this.extrema)
|
||||
}
|
||||
|
||||
getBox(box?: Box3D) {
|
||||
if (!box) box = Box3D()
|
||||
Box3D.setEmpty(box)
|
||||
for (let i = 0; i < this.extrema.length; i++) {
|
||||
Box3D.add(box, this.extrema[i])
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
reset() {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.minDist[i] = Infinity
|
||||
this.maxDist[i] = -Infinity
|
||||
this.extrema[i * 2] = Vec3()
|
||||
this.extrema[i * 2 + 1] = Vec3()
|
||||
}
|
||||
this.centroidHelper.reset()
|
||||
}
|
||||
|
||||
constructor(quality: EposQuality) {
|
||||
this.dir = getEposDir(quality)
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
|
||||
function updateExtremeMin(d: number, e: Vec3, center: Vec3, r: number) {
|
||||
if (center[d] - r < e[d]) {
|
||||
Vec3.copy(e, center);
|
||||
e[d] -= r;
|
||||
type EposQuality = '6' | '14' | '26' | '98'
|
||||
|
||||
function getEposDir(quality: EposQuality) {
|
||||
let dir: number[][]
|
||||
switch (quality) {
|
||||
case '6': dir = [ ...Type001 ]; break
|
||||
case '14': dir = [ ...Type001, ...Type111 ]; break
|
||||
case '26': dir = [ ...Type001, ...Type111, ...Type011 ]; break
|
||||
case '98': dir = [ ...Type001, ...Type111, ...Type011, ...Type012, ...Type112, ...Type122 ]; break
|
||||
}
|
||||
return dir.map(a => {
|
||||
const v = Vec3.create(a[0], a[1], a[2])
|
||||
return Vec3.normalize(v, v)
|
||||
})
|
||||
}
|
||||
|
||||
function updateExtremeMax(d: number, e: Vec3, center: Vec3, r: number) {
|
||||
if (center[d] + r > e[d]) {
|
||||
Vec3.copy(e, center);
|
||||
e[d] += r;
|
||||
}
|
||||
}
|
||||
const Type001 = [
|
||||
[1, 0, 0], [0, 1, 0], [0, 0, 1]
|
||||
]
|
||||
|
||||
const Type111 = [
|
||||
[1, 1, 1], [-1, 1, 1], [-1, -1, 1], [1, -1, 1]
|
||||
]
|
||||
|
||||
const Type011 = [
|
||||
[1, 1, 0], [1, -1, 0], [1, 0, 1], [1, 0, -1], [0, 1, 1], [0, 1, -1]
|
||||
]
|
||||
|
||||
const Type012 = [
|
||||
[0, 1, 2], [0, 2, 1], [1, 0, 2], [2, 0, 1], [1, 2, 0], [2, 1, 0],
|
||||
[0, 1, -2], [0, 2, -1], [1, 0, -2], [2, 0, -1], [1, -2, 0], [2, -1, 0]
|
||||
]
|
||||
|
||||
const Type112 = [
|
||||
[1, 1, 2], [2, 1, 1], [1, 2, 1], [1, -1, 2], [1, 1, -2], [1, -1, -2],
|
||||
[2, -1, 1], [2, 1, -1], [2, -1, -1], [1, -2, 1], [1, 2, -1], [1, -2, -1]
|
||||
]
|
||||
|
||||
const Type122 = [
|
||||
[2, 2, 1], [1, 2, 2], [2, 1, 2], [2, -2, 1], [2, 2, -1], [2, -2, -1],
|
||||
[1, -2, 2], [1, 2, -2], [1, -2, -2], [2, -1, 2], [2, 1, -2], [2, -1, -2]
|
||||
]
|
||||
52
src/mol-math/geometry/boundary.ts
Normal file
52
src/mol-math/geometry/boundary.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PositionData } from './common';
|
||||
import { Vec3 } from '../linear-algebra';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { BoundaryHelper } from './boundary-helper';
|
||||
import { Box3D, Sphere3D } from '../geometry';
|
||||
|
||||
const boundaryHelperCoarse = new BoundaryHelper('14');
|
||||
const boundaryHelperFine = new BoundaryHelper('98');
|
||||
function getBoundaryHelper(count: number) {
|
||||
return count > 100_000 ? boundaryHelperCoarse : boundaryHelperFine
|
||||
}
|
||||
|
||||
export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
|
||||
|
||||
export function getBoundary(data: PositionData): Boundary {
|
||||
const { x, y, z, radius, indices } = data;
|
||||
const p = Vec3();
|
||||
|
||||
const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
|
||||
boundaryHelper.reset();
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
Vec3.set(p, x[i], y[i], z[i]);
|
||||
boundaryHelper.includePositionRadius(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
Vec3.set(p, x[i], y[i], z[i]);
|
||||
boundaryHelper.radiusPositionRadius(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
|
||||
if (!radius && OrderedSet.size(indices) <= 98) {
|
||||
const extrema: Vec3[] = []
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
extrema.push(Vec3.create(x[i], y[i], z[i]));
|
||||
}
|
||||
Sphere3D.setExtrema(sphere, extrema)
|
||||
}
|
||||
|
||||
return { box: boundaryHelper.getBox(), sphere };
|
||||
}
|
||||
@@ -37,13 +37,24 @@ class CentroidHelper {
|
||||
if (d > this.radiusSq) this.radiusSq = d;
|
||||
}
|
||||
|
||||
getSphere(): Sphere3D {
|
||||
return { center: Vec3.clone(this.center), radius: Math.sqrt(this.radiusSq) };
|
||||
radiusSphereStep(center: Vec3, radius: number) {
|
||||
const _d = Vec3.distance(center, this.center) + radius;
|
||||
const d = _d * _d;
|
||||
if (d > this.radiusSq) this.radiusSq = d;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
||||
getSphere(sphere?: Sphere3D): Sphere3D {
|
||||
if (!sphere) sphere = Sphere3D()
|
||||
Vec3.copy(sphere.center, this.center)
|
||||
sphere.radius = Math.sqrt(this.radiusSq)
|
||||
return sphere
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
namespace CentroidHelper {
|
||||
|
||||
@@ -46,7 +46,7 @@ export const GaussianDensitySchema = {
|
||||
}
|
||||
|
||||
export const GaussianDensityShaderCode = ShaderCode(
|
||||
gaussian_density_vert, gaussian_density_frag,
|
||||
'gaussian-density', gaussian_density_vert, gaussian_density_frag,
|
||||
{ standardDerivatives: false, fragDepth: false }
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -11,14 +11,14 @@ import { Sphere3D } from '../primitives/sphere3d';
|
||||
import { PositionData } from '../common';
|
||||
import { Vec3 } from '../../linear-algebra';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { BoundaryHelper } from '../boundary-helper';
|
||||
import { Boundary } from '../../../mol-model/structure/structure/util/boundary';
|
||||
|
||||
interface GridLookup3D<T = number> extends Lookup3D<T> {
|
||||
readonly buckets: { readonly offset: ArrayLike<number>, readonly count: ArrayLike<number>, readonly array: ArrayLike<number> }
|
||||
}
|
||||
|
||||
function GridLookup3D<T extends number = number>(data: PositionData, cellSizeOrCount?: Vec3 | number): GridLookup3D<T> {
|
||||
return new GridLookup3DImpl<T>(data, cellSizeOrCount);
|
||||
function GridLookup3D<T extends number = number>(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number): GridLookup3D<T> {
|
||||
return new GridLookup3DImpl<T>(data, boundary, cellSizeOrCount);
|
||||
}
|
||||
|
||||
export { GridLookup3D }
|
||||
@@ -48,8 +48,8 @@ class GridLookup3DImpl<T extends number = number> implements GridLookup3D<T> {
|
||||
return query(this.ctx);
|
||||
}
|
||||
|
||||
constructor(data: PositionData, cellSizeOrCount?: Vec3 | number) {
|
||||
const structure = build(data, cellSizeOrCount);
|
||||
constructor(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number) {
|
||||
const structure = build(data, boundary, cellSizeOrCount);
|
||||
this.ctx = createContext<T>(structure);
|
||||
this.boundary = { box: structure.boundingBox, sphere: structure.boundingSphere };
|
||||
this.buckets = { offset: structure.bucketOffset, count: structure.bucketCounts, array: structure.bucketArray };
|
||||
@@ -166,30 +166,9 @@ function _build(state: BuildState): Grid3D {
|
||||
}
|
||||
}
|
||||
|
||||
const boundaryHelper = new BoundaryHelper();
|
||||
function getBoundary(data: PositionData) {
|
||||
const { x, y, z, radius, indices } = data;
|
||||
const p = Vec3();
|
||||
boundaryHelper.reset(0);
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
Vec3.set(p, x[i], y[i], z[i]);
|
||||
boundaryHelper.boundaryStep(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep();
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
Vec3.set(p, x[i], y[i], z[i]);
|
||||
boundaryHelper.extendStep(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
|
||||
return { boundingBox: boundaryHelper.getBox(), boundingSphere: boundaryHelper.getSphere() };
|
||||
}
|
||||
|
||||
function build(data: PositionData, cellSizeOrCount?: Vec3 | number) {
|
||||
const { boundingBox, boundingSphere } = getBoundary(data);
|
||||
function build(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number) {
|
||||
// need to expand the grid bounds to avoid rounding errors
|
||||
const expandedBox = Box3D.expand(Box3D.empty(), boundingBox, Vec3.create(0.5, 0.5, 0.5));
|
||||
const expandedBox = Box3D.expand(Box3D.empty(), boundary.box, Vec3.create(0.5, 0.5, 0.5));
|
||||
const { indices } = data;
|
||||
|
||||
const S = Box3D.size(Vec3.zero(), expandedBox);
|
||||
@@ -227,8 +206,8 @@ function build(data: PositionData, cellSizeOrCount?: Vec3 | number) {
|
||||
size,
|
||||
data: inputData,
|
||||
expandedBox,
|
||||
boundingBox,
|
||||
boundingSphere,
|
||||
boundingBox: boundary.box,
|
||||
boundingSphere: boundary.sphere,
|
||||
elementCount,
|
||||
delta
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Fred Ludlow <fred.ludlow@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -14,6 +14,8 @@ import { OrderedSet } from '../../mol-data/int';
|
||||
import { PositionData } from './common';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra/3d';
|
||||
import { Box3D, GridLookup3D, fillGridDim } from '../../mol-math/geometry';
|
||||
import { BaseGeometry } from '../../mol-geo/geometry/base';
|
||||
import { Boundary } from '../../mol-model/structure/structure/util/boundary';
|
||||
|
||||
function normalToLine (out: Vec3, p: Vec3) {
|
||||
out[0] = out[1] = out[2] = 1.0
|
||||
@@ -45,15 +47,15 @@ function getAngleTables (probePositions: number): AnglesTables {
|
||||
//
|
||||
|
||||
export const MolecularSurfaceCalculationParams = {
|
||||
resolution: PD.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }),
|
||||
probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }),
|
||||
probePositions: PD.Numeric(30, { min: 12, max: 90, step: 1 }),
|
||||
probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }, { description: 'Radius of the probe tracing the molecular surface.' }),
|
||||
resolution: PD.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }, { description: 'Grid resolution/cell spacing.', ...BaseGeometry.CustomQualityParamInfo }),
|
||||
probePositions: PD.Numeric(30, { min: 12, max: 90, step: 1 }, { description: 'Number of positions tested for probe target intersection.', ...BaseGeometry.CustomQualityParamInfo }),
|
||||
}
|
||||
export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(MolecularSurfaceCalculationParams)
|
||||
export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps
|
||||
|
||||
|
||||
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, props: MolecularSurfaceCalculationProps) {
|
||||
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, boundary: Boundary, maxRadius: number, box: Box3D | null, props: MolecularSurfaceCalculationProps) {
|
||||
// Field generation method adapted from AstexViewer (Mike Hartshorn) by Fred Ludlow.
|
||||
// Other parts based heavily on NGL (Alexander Rose) EDT Surface class
|
||||
|
||||
@@ -78,7 +80,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
}
|
||||
|
||||
for (let j = 0, jl = neighbours.count; j < jl; ++j) {
|
||||
const ai = OrderedSet.getAt(position.indices, neighbours.indices[j])
|
||||
const ai = OrderedSet.getAt(indices, neighbours.indices[j])
|
||||
if (ai !== a && ai !== b && singleAtomObscures(ai, x, y, z)) {
|
||||
lastClip = ai
|
||||
return ai
|
||||
@@ -281,7 +283,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
// Is this grid point closer to a or b?
|
||||
// Take dot product of atob and gridpoint->p (dx, dy, dz)
|
||||
const dp = dx * atob[0] + dy * atob[1] + dz * atob[2]
|
||||
idData[idx] = id[OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)]
|
||||
idData[idx] = id[OrderedSet.indexOf(indices, dp < 0.0 ? b : a)]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,15 +319,16 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const scaleFactor = 1 / resolution
|
||||
const ngTorus = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor))
|
||||
|
||||
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius)
|
||||
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius)
|
||||
Vec3.scale(cellSize, cellSize, 2)
|
||||
const lookup3d = GridLookup3D(position, cellSize)
|
||||
const lookup3d = GridLookup3D(position, boundary, cellSize)
|
||||
const neighbours = lookup3d.result
|
||||
const box = lookup3d.boundary.box
|
||||
if (box === null) box = lookup3d.boundary.box
|
||||
|
||||
const { indices, x: px, y: py, z: pz, id, radius } = position
|
||||
const n = OrderedSet.size(indices)
|
||||
|
||||
const pad = maxRadius * 2 + resolution
|
||||
const pad = maxRadius + resolution
|
||||
const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
|
||||
const [ minX, minY, minZ ] = expandedBox.min
|
||||
const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Vec3, Mat4 } from '../../linear-algebra'
|
||||
import { PositionData } from '../common'
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { Sphere3D } from './sphere3d';
|
||||
|
||||
interface Box3D { min: Vec3, max: Vec3 }
|
||||
|
||||
@@ -29,6 +30,13 @@ namespace Box3D {
|
||||
return copy(empty(), a);
|
||||
}
|
||||
|
||||
export function fromSphere3D(out: Box3D, sphere: Sphere3D): Box3D {
|
||||
const r = Vec3.create(sphere.radius, sphere.radius, sphere.radius)
|
||||
Vec3.sub(out.min, sphere.center, r)
|
||||
Vec3.add(out.max, sphere.center, r)
|
||||
return out
|
||||
}
|
||||
|
||||
export function computeBounding(data: PositionData): Box3D {
|
||||
const min = Vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
|
||||
const max = Vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -8,33 +8,57 @@
|
||||
import { Vec3, Mat4, EPSILON } from '../../linear-algebra'
|
||||
import { PositionData } from '../common'
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
|
||||
import { Box3D } from './box3d';
|
||||
import { Axes3D } from './axes3d';
|
||||
|
||||
interface Sphere3D { center: Vec3, radius: number }
|
||||
interface Sphere3D {
|
||||
center: Vec3,
|
||||
radius: number,
|
||||
extrema?: Vec3[]
|
||||
}
|
||||
|
||||
function Sphere3D() {
|
||||
return Sphere3D.zero();
|
||||
}
|
||||
|
||||
namespace Sphere3D {
|
||||
export function hasExtrema(sphere: Sphere3D): sphere is PickRequired<Sphere3D, 'extrema'> {
|
||||
return sphere.extrema !== undefined
|
||||
}
|
||||
|
||||
export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
|
||||
export function zero(): Sphere3D { return { center: Vec3.zero(), radius: 0 }; }
|
||||
export function zero(): Sphere3D { return { center: Vec3(), radius: 0 }; }
|
||||
|
||||
export function clone(a: Sphere3D): Sphere3D {
|
||||
const out = zero();
|
||||
Vec3.copy(out.center, a.center);
|
||||
out.radius = a.radius
|
||||
const out = create(Vec3.clone(a.center), a.radius)
|
||||
if (hasExtrema(a)) out.extrema = a.extrema
|
||||
return out;
|
||||
}
|
||||
|
||||
export function set(out: Sphere3D, center: Vec3, radius: number) {
|
||||
Vec3.copy(out.center, center)
|
||||
out.radius = radius
|
||||
return out
|
||||
}
|
||||
|
||||
export function copy(out: Sphere3D, a: Sphere3D) {
|
||||
Vec3.copy(out.center, a.center)
|
||||
out.radius = a.radius
|
||||
if (hasExtrema(a)) setExtrema(out, a.extrema)
|
||||
return out;
|
||||
}
|
||||
|
||||
export function setExtrema(out: Sphere3D, extrema: Vec3[]): Sphere3D {
|
||||
if (out.extrema !== undefined) {
|
||||
out.extrema.length = 0
|
||||
out.extrema.push(...extrema)
|
||||
} else {
|
||||
out.extrema = [...extrema]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export function computeBounding(data: PositionData): Sphere3D {
|
||||
const { x, y, z, indices } = data;
|
||||
let cx = 0, cy = 0, cz = 0;
|
||||
@@ -110,13 +134,28 @@ namespace Sphere3D {
|
||||
export function expandBySphere(out: Sphere3D, sphere: Sphere3D, by: Sphere3D) {
|
||||
Vec3.copy(out.center, sphere.center)
|
||||
out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius)
|
||||
if (hasExtrema(sphere) && hasExtrema(by)) {
|
||||
setExtrema(out, [
|
||||
...sphere.extrema.map(e => Vec3.clone(e)),
|
||||
...by.extrema.map(e => Vec3.clone(e))
|
||||
])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const tmpDir = Vec3()
|
||||
/** Expand sphere radius by delta */
|
||||
export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
|
||||
Vec3.copy(out.center, sphere.center)
|
||||
out.radius = sphere.radius + delta
|
||||
if (hasExtrema(sphere)) {
|
||||
setExtrema(out, sphere.extrema.map(e => {
|
||||
Vec3.sub(tmpDir, e, sphere.center)
|
||||
const dist = Vec3.distance(sphere.center, e)
|
||||
Vec3.normalize(tmpDir, tmpDir)
|
||||
return Vec3.scaleAndAdd(Vec3(), sphere.center, tmpDir, dist + delta)
|
||||
}))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -136,6 +175,30 @@ namespace Sphere3D {
|
||||
return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
|
||||
Vec3.equals(a.center, b.center));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if `a` includes `b`, use `extrema` of `b` when available
|
||||
*/
|
||||
export function includes(a: Sphere3D, b: Sphere3D) {
|
||||
if (hasExtrema(b)) {
|
||||
for (const e of b.extrema) {
|
||||
if (Vec3.distance(a.center, e) > a.radius) return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return Vec3.distance(a.center, b.center) + b.radius <= a.radius
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if `a` and `b` are overlapping */
|
||||
export function overlaps(a: Sphere3D, b: Sphere3D) {
|
||||
return Vec3.distance(a.center, b.center) <= a.radius + b.radius
|
||||
}
|
||||
|
||||
/** Get the signed distance of `a` and `b` */
|
||||
export function distance(a: Sphere3D, b: Sphere3D) {
|
||||
return Vec3.distance(a.center, b.center) - a.radius + b.radius
|
||||
}
|
||||
}
|
||||
|
||||
export { Sphere3D }
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -35,7 +35,8 @@ namespace SpacegroupCell {
|
||||
export const Zero: SpacegroupCell = create('P 1', Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
|
||||
|
||||
/** True if 'P 1' with cellsize [1, 1, 1] */
|
||||
export function isZero(cell: SpacegroupCell) {
|
||||
export function isZero(cell?: SpacegroupCell) {
|
||||
if (!cell) return true;
|
||||
return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
|
||||
}
|
||||
|
||||
@@ -103,9 +104,9 @@ namespace Spacegroup {
|
||||
);
|
||||
}
|
||||
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = setOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = setOperatorMatrix(spacegroup, spgrOp, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
}
|
||||
|
||||
const _translationRef = Vec3()
|
||||
@@ -134,9 +135,9 @@ namespace Spacegroup {
|
||||
* Get Symmetry operator for transformation around the given
|
||||
* reference point `ref` in fractional coordinates
|
||||
*/
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, index, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
@@ -145,6 +146,54 @@ namespace Spacegroup {
|
||||
const r3 = TransformData[ids[2]];
|
||||
return Mat4.ofRows([r1, r2, r3, [0, 0, 0, 1]]);
|
||||
}
|
||||
|
||||
export function getOperatorXyz(op: Mat4) {
|
||||
return [
|
||||
formatElement(getRotation(op[0], op[4], op[8]), getShift(op[12])),
|
||||
formatElement(getRotation(op[1], op[5], op[9]), getShift(op[13])),
|
||||
formatElement(getRotation(op[2], op[6], op[10]), getShift(op[14]))
|
||||
].join(',')
|
||||
}
|
||||
|
||||
function getRotation(x: number, y: number, z: number) {
|
||||
let r: string[] = []
|
||||
if (x > 0) r.push('+X')
|
||||
else if (x < 0) r.push('-X')
|
||||
if (y > 0) r.push('+Y')
|
||||
else if (y < 0) r.push('-Y')
|
||||
if (z > 0) r.push('+Z')
|
||||
else if (z < 0) r.push('-Z')
|
||||
|
||||
if (r.length === 1) {
|
||||
return r[0].charAt(0) === '+' ? r[0].substr(1) : r[0]
|
||||
}
|
||||
if (r.length === 2) {
|
||||
const s0 = r[0].charAt(0)
|
||||
const s1 = r[1].charAt(0)
|
||||
if (s0 === '+') return `${r[0].substr(1)}${r[1]}`
|
||||
if (s1 === '+') return `${r[1].substr(1)}${r[0]}`
|
||||
}
|
||||
throw new Error(`unknown rotation '${r}', ${x} ${y} ${z}`)
|
||||
}
|
||||
|
||||
function getShift(s: number) {
|
||||
switch (s) {
|
||||
case 1/2: return '1/2'
|
||||
case 1/4: return '1/4'
|
||||
case 3/4: return '3/4'
|
||||
case 1/3: return '1/3'
|
||||
case 2/3: return '2/3'
|
||||
case 1/6: return '1/6'
|
||||
case 5/6: return '5/6'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function formatElement(rotation: string, shift: string) {
|
||||
if (shift === '') return rotation
|
||||
if (rotation.length > 2) return `${rotation}+${shift}`
|
||||
return rotation.charAt(0) === '-' ? `${shift}${rotation}` : `${shift}+${rotation}`
|
||||
}
|
||||
}
|
||||
|
||||
export { Spacegroup, SpacegroupCell }
|
||||
@@ -11,12 +11,14 @@ import { defaults } from '../../mol-util';
|
||||
interface SymmetryOperator {
|
||||
readonly name: string,
|
||||
|
||||
readonly assembly: {
|
||||
readonly assembly?: {
|
||||
/** pointer to `pdbx_struct_assembly.id` or empty string */
|
||||
readonly id: string
|
||||
readonly id: string,
|
||||
/** pointers to `pdbx_struct_oper_list.id` or empty list */
|
||||
readonly operList: string[]
|
||||
}
|
||||
readonly operList: string[],
|
||||
/** (arbitrary) unique id of the operator to be used in suffix */
|
||||
readonly operId: number
|
||||
},
|
||||
|
||||
/** pointer to `struct_ncs_oper.id` or empty string */
|
||||
readonly ncsId: string,
|
||||
@@ -29,22 +31,52 @@ interface SymmetryOperator {
|
||||
// cache the inverse of the transform
|
||||
readonly inverse: Mat4,
|
||||
// optimize the identity case
|
||||
readonly isIdentity: boolean
|
||||
readonly isIdentity: boolean,
|
||||
|
||||
/**
|
||||
* Suffix based on operator type.
|
||||
* - Assembly: _assembly.operId
|
||||
* - Crytal: -op_ijk
|
||||
* - ncs: _ncsId
|
||||
*/
|
||||
readonly suffix: string
|
||||
}
|
||||
|
||||
namespace SymmetryOperator {
|
||||
export const DefaultName = '1_555'
|
||||
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity(), { id: '', operList: [] });
|
||||
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
|
||||
|
||||
export const RotationTranslationEpsilon = 0.005;
|
||||
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
|
||||
export type CreateInfo = { assembly?: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number }
|
||||
export function create(name: string, matrix: Mat4, info?: CreateInfo): SymmetryOperator {
|
||||
let { assembly, ncsId, hkl, spgrOp } = info || { };
|
||||
const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
|
||||
spgrOp = defaults(spgrOp, -1)
|
||||
ncsId = ncsId || ''
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
|
||||
spgrOp = defaults(spgrOp, -1);
|
||||
ncsId = ncsId || '';
|
||||
const suffix = getSuffix(info);
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
}
|
||||
|
||||
function getSuffix(info?: CreateInfo) {
|
||||
if (!info) return '';
|
||||
|
||||
if (info.assembly) {
|
||||
return `_${info.assembly.operId}`;
|
||||
}
|
||||
|
||||
if (typeof info.spgrOp !== 'undefined' && typeof info.hkl !== 'undefined' && info.spgrOp !== -1) {
|
||||
const [i, j, k] = info.hkl;
|
||||
return `-${info.spgrOp + 1}_${5 + i}${5 + j}${5 + k}`
|
||||
}
|
||||
|
||||
if (info.ncsId) {
|
||||
return `_${info.ncsId}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
|
||||
@@ -66,7 +98,7 @@ namespace SymmetryOperator {
|
||||
}
|
||||
}
|
||||
Mat4.setTranslation(t, offset);
|
||||
return create(name, t, { id: '', operList: [] }, ncsId);
|
||||
return create(name, t, { ncsId });
|
||||
}
|
||||
|
||||
const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero();
|
||||
@@ -114,7 +146,7 @@ namespace SymmetryOperator {
|
||||
*/
|
||||
export function compose(first: SymmetryOperator, second: SymmetryOperator) {
|
||||
const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
|
||||
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
|
||||
return create(second.name, matrix, second);
|
||||
}
|
||||
|
||||
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
|
||||
|
||||
@@ -254,6 +254,31 @@ namespace Mat4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function extractRotation(out: Mat4, mat: Mat4) {
|
||||
const scaleX = 1 / Math.sqrt(mat[0] * mat[0] + mat[1] * mat[1] + mat[2] * mat[2]);
|
||||
const scaleY = 1 / Math.sqrt(mat[4] * mat[4] + mat[5] * mat[5] + mat[6] * mat[6]);
|
||||
const scaleZ = 1 / Math.sqrt(mat[8] * mat[8] + mat[9] * mat[9] + mat[10] * mat[10]);
|
||||
|
||||
out[0] = mat[0] * scaleX;
|
||||
out[1] = mat[1] * scaleX;
|
||||
out[2] = mat[2] * scaleX;
|
||||
out[3] = 0;
|
||||
out[4] = mat[4] * scaleY;
|
||||
out[5] = mat[5] * scaleY;
|
||||
out[6] = mat[6] * scaleY;
|
||||
out[7] = 0;
|
||||
out[8] = mat[8] * scaleZ;
|
||||
out[9] = mat[9] * scaleZ;
|
||||
out[10] = mat[10] * scaleZ;
|
||||
out[11] = 0;
|
||||
out[12] = 0;
|
||||
out[13] = 0;
|
||||
out[14] = 0;
|
||||
out[15] = 1;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function transpose(out: Mat4, a: Mat4) {
|
||||
// If we are transposing ourselves we can skip a few steps but have to cache some values
|
||||
if (out === a) {
|
||||
|
||||
@@ -436,7 +436,7 @@ namespace Vec3 {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Computes the angle between 2 vectors, reports in rad. */
|
||||
/** Computes the angle between 2 vectors, reports in radians. */
|
||||
export function angle(a: Vec3, b: Vec3) {
|
||||
const theta = dot(a, b) / Math.sqrt(squaredMagnitude(a) * squaredMagnitude(b));
|
||||
return Math.acos(clamp(theta, -1, 1)); // clamp to avoid numerical problems
|
||||
@@ -450,7 +450,7 @@ namespace Vec3 {
|
||||
const tmp_dh_bcd = zero();
|
||||
const tmp_dh_cross = zero();
|
||||
/**
|
||||
* Computes the dihedral angles of 4 points.
|
||||
* Computes the dihedral angles of 4 points, reports in radians.
|
||||
*/
|
||||
export function dihedralAngle(a: Vec3, b: Vec3, c: Vec3, d: Vec3): number {
|
||||
sub(tmp_dh_ab, a, b);
|
||||
@@ -461,7 +461,7 @@ namespace Vec3 {
|
||||
cross(tmp_dh_abc, tmp_dh_ab, tmp_dh_cb);
|
||||
cross(tmp_dh_bcd, tmp_dh_bc, tmp_dh_dc);
|
||||
|
||||
const _angle = angle(tmp_dh_abc, tmp_dh_bcd) * 360.0 / (2 * Math.PI);
|
||||
const _angle = angle(tmp_dh_abc, tmp_dh_bcd);
|
||||
cross(tmp_dh_cross, tmp_dh_abc, tmp_dh_bcd);
|
||||
return dot(tmp_dh_cb, tmp_dh_cross) > 0 ? _angle : -_angle;
|
||||
}
|
||||
@@ -546,6 +546,8 @@ namespace Vec3 {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
|
||||
}
|
||||
|
||||
export const origin: ReadonlyVec3 = Vec3.create(0, 0, 0)
|
||||
|
||||
export const unit: ReadonlyVec3 = Vec3.create(1, 1, 1)
|
||||
export const negUnit: ReadonlyVec3 = Vec3.create(-1, -1, -1)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace PrincipalAxes {
|
||||
|
||||
export function calculateMomentsAxes(positions: NumberArray): Axes3D {
|
||||
if (positions.length === 3) {
|
||||
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3(), Vec3(), Vec3())
|
||||
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3.create(1, 0, 0), Vec3.create(0, 1, 0), Vec3.create(0, 1, 0))
|
||||
}
|
||||
|
||||
const points = Matrix.fromArray(positions, 3, positions.length / 3)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { Interval, Segmentation } from '../../../mol-data/int';
|
||||
import UUID from '../../../mol-util/uuid';
|
||||
import { ElementIndex } from '../../../mol-model/structure';
|
||||
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
|
||||
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
|
||||
@@ -16,6 +16,12 @@ import { ElementSymbol } from '../../../mol-model/structure/model/types';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
|
||||
import { AtomSite } from './schema';
|
||||
import { ModelFormat } from '../format';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { MmcifFormat } from '../mmcif';
|
||||
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
|
||||
import { toDatabase } from '../../../mol-io/reader/cif/schema';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
|
||||
function findHierarchyOffsets(atom_site: AtomSite) {
|
||||
if (atom_site._rowCount === 0) return { residues: [], chains: [] };
|
||||
@@ -90,19 +96,75 @@ function getConformation(atom_site: AtomSite): AtomicConformation {
|
||||
|
||||
function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
|
||||
// TODO need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
|
||||
return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>)
|
||||
&& Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
|
||||
&& Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
|
||||
return Table.areEqual(a.chains, b.chains)
|
||||
&& Table.areEqual(a.residues, b.residues)
|
||||
&& Table.areEqual(a.atoms, b.atoms)
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
function createChainOperatorMappingAndSubstituteNames(hierarchy: AtomicData, format: ModelFormat) {
|
||||
const mapping = new Map<ChainIndex, SymmetryOperator>();
|
||||
if (!MmcifFormat.is(format)) return mapping;
|
||||
|
||||
const { molstar_atom_site_operator_mapping: entries } = toDatabase(AtomSiteOperatorMappingSchema, format.data.frame);
|
||||
if (entries._rowCount === 0) return mapping;
|
||||
|
||||
const labelMap = new Map<string, { name: string, operator: SymmetryOperator }>();
|
||||
const authMap = new Map<string, string>();
|
||||
|
||||
for (let i = 0; i < entries._rowCount; i++) {
|
||||
const assembly: SymmetryOperator['assembly'] = entries.assembly_operator_id.valueKind(i) === Column.ValueKind.Present
|
||||
? { id: entries.assembly_id.value(i), operList: [], operId: entries.assembly_operator_id.value(i) }
|
||||
: void 0;
|
||||
|
||||
const operator = SymmetryOperator.create(entries.operator_name.value(i), Mat4.identity(), {
|
||||
assembly,
|
||||
spgrOp: entries.symmetry_operator_index.valueKind(i) === Column.ValueKind.Present ? entries.symmetry_operator_index.value(i) : void 0,
|
||||
hkl: Vec3.ofArray(entries.symmetry_hkl.value(i)),
|
||||
ncsId: entries.ncs_id.value(i)
|
||||
});
|
||||
|
||||
const suffix = entries.suffix.value(i);
|
||||
const label = entries.label_asym_id.value(i);
|
||||
labelMap.set(`${label}${suffix}`, { name: label, operator });
|
||||
const auth = entries.auth_asym_id.value(i);
|
||||
authMap.set(`${auth}${suffix}`, auth);
|
||||
}
|
||||
|
||||
const { label_asym_id, auth_asym_id } = hierarchy.chains;
|
||||
const mappedLabel: string[] = new Array(label_asym_id.rowCount);
|
||||
const mappedAuth: string[] = new Array(label_asym_id.rowCount);
|
||||
|
||||
for (let i = 0 as ChainIndex; i < label_asym_id.rowCount; i++) {
|
||||
const label = label_asym_id.value(i), auth = auth_asym_id.value(i);
|
||||
if (!labelMap.has(label)) {
|
||||
mappedLabel[i] = label;
|
||||
mappedAuth[i] = auth;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, operator } = labelMap.get(label)!;
|
||||
mapping.set(i, operator);
|
||||
|
||||
mappedLabel[i] = name;
|
||||
mappedAuth[i] = authMap.get(auth) || auth;
|
||||
}
|
||||
|
||||
hierarchy.chains.label_asym_id = Column.ofArray({ array: mappedLabel, valueKind: hierarchy.chains.label_asym_id.valueKind, schema: hierarchy.chains.label_asym_id.schema });
|
||||
hierarchy.chains.auth_asym_id = Column.ofArray({ array: mappedAuth, valueKind: hierarchy.chains.auth_asym_id.valueKind, schema: hierarchy.chains.auth_asym_id.schema });
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
|
||||
const hierarchyOffsets = findHierarchyOffsets(atom_site);
|
||||
const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
|
||||
const chainOperatorMapping = createChainOperatorMappingAndSubstituteNames(hierarchyData, format);
|
||||
|
||||
if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
|
||||
return {
|
||||
sameAsPrevious: true,
|
||||
hierarchy: previous.atomicHierarchy,
|
||||
chainOperatorMapping
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,11 +176,11 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
|
||||
const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
|
||||
const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
|
||||
const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
|
||||
return { sameAsPrevious: false, hierarchy };
|
||||
return { sameAsPrevious: false, hierarchy, chainOperatorMapping };
|
||||
}
|
||||
|
||||
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous)
|
||||
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
|
||||
const { sameAsPrevious, hierarchy, chainOperatorMapping } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, format, previous)
|
||||
const conformation = getConformation(atom_site)
|
||||
return { sameAsPrevious, hierarchy, conformation };
|
||||
return { sameAsPrevious, hierarchy, conformation, chainOperatorMapping };
|
||||
}
|
||||
@@ -24,15 +24,22 @@ import { getModelGroupName } from './util';
|
||||
|
||||
export async function createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
|
||||
const properties = getProperties(data)
|
||||
return data.ihm_model_list._rowCount > 0
|
||||
const models = data.ihm_model_list._rowCount > 0
|
||||
? await readIntegrative(ctx, data, properties, format)
|
||||
: await readStandard(ctx, data, properties, format);
|
||||
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
models[i].trajectoryInfo.index = i;
|
||||
models[i].trajectoryInfo.size = models.length;
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
/** Standard atomic model */
|
||||
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
|
||||
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, previous);
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
|
||||
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
|
||||
if (previous && atomic.sameAsPrevious) {
|
||||
return {
|
||||
@@ -45,7 +52,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
}
|
||||
|
||||
const coarse = EmptyCoarse;
|
||||
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId)
|
||||
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy)
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence)
|
||||
|
||||
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
@@ -62,11 +69,13 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
entry,
|
||||
sourceData: format,
|
||||
modelNum,
|
||||
trajectoryInfo: { index: 0, size: 1 },
|
||||
entities,
|
||||
sequence,
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
atomicRanges,
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
@@ -78,9 +87,9 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
|
||||
/** Integrative model with atomic/coarse parts */
|
||||
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap);
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
|
||||
const coarse = getCoarse(ihm, properties);
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId)
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy)
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence)
|
||||
|
||||
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
@@ -99,11 +108,13 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
entry,
|
||||
sourceData: format,
|
||||
modelNum: ihm.model_id,
|
||||
trajectoryInfo: { index: 0, size: 1 },
|
||||
entities: ihm.entities,
|
||||
sequence,
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
atomicRanges,
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
|
||||
@@ -6,30 +6,13 @@
|
||||
*/
|
||||
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common';
|
||||
import { ChemicalComponent, MissingResidue, StructAsym } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getMoleculeType, MoleculeType, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
|
||||
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { memoize1 } from '../../../mol-util/memoize';
|
||||
import { BasicData } from './schema';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
|
||||
function getModifiedResidueNameMap(data: BasicData): Model['properties']['modifiedResidues'] {
|
||||
const parentId = new Map<string, string>();
|
||||
const details = new Map<string, string>();
|
||||
|
||||
const c = data.pdbx_struct_mod_residue;
|
||||
const comp_id = c.label_comp_id.isDefined ? c.label_comp_id : c.auth_comp_id;
|
||||
const parent_id = c.parent_comp_id, details_data = c.details;
|
||||
|
||||
for (let i = 0; i < c._rowCount; i++) {
|
||||
const id = comp_id.value(i);
|
||||
parentId.set(id, parent_id.value(i));
|
||||
details.set(id, details_data.value(i));
|
||||
}
|
||||
|
||||
return { parentId, details };
|
||||
}
|
||||
|
||||
function getMissingResidues(data: BasicData): Model['properties']['missingResidues'] {
|
||||
const map = new Map<string, MissingResidue>();
|
||||
const getKey = (model_num: number, asym_id: string, seq_id: number) => {
|
||||
@@ -124,11 +107,43 @@ const getUniqueComponentNames = memoize1((data: BasicData) => {
|
||||
return uniqueNames
|
||||
})
|
||||
|
||||
|
||||
function getStructAsymMap(data: BasicData): Model['properties']['structAsymMap'] {
|
||||
const map = new Map<string, StructAsym>();
|
||||
|
||||
const { label_asym_id, auth_asym_id, label_entity_id } = data.atom_site
|
||||
for (let i = 0, il = label_asym_id.rowCount; i < il; ++i) {
|
||||
const id = label_asym_id.value(i)
|
||||
if (!map.has(id)) {
|
||||
map.set(id, {
|
||||
id,
|
||||
auth_id: auth_asym_id.value(i),
|
||||
entity_id: label_entity_id.value(i)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (data.struct_asym._rowCount > 0) {
|
||||
const { id, entity_id } = data.struct_asym
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
const _id = id.value(i)
|
||||
if (!map.has(_id)) {
|
||||
map.set(_id, {
|
||||
id: _id,
|
||||
auth_id: '',
|
||||
entity_id: entity_id.value(i)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
export function getProperties(data: BasicData): Model['properties'] {
|
||||
return {
|
||||
modifiedResidues: getModifiedResidueNameMap(data),
|
||||
missingResidues: getMissingResidues(data),
|
||||
chemicalComponentMap: getChemicalComponentMap(data),
|
||||
saccharideComponentMap: getSaccharideComponentMap(data)
|
||||
saccharideComponentMap: getSaccharideComponentMap(data),
|
||||
structAsymMap: getStructAsymMap(data)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
|
||||
// TODO split into conformation and hierarchy parts
|
||||
// TODO extract `pdbx_struct_mod_residue` as property?
|
||||
|
||||
export type Entry = Table<mmCIF_Schema['entry']>
|
||||
export type Struct = Table<mmCIF_Schema['struct']>
|
||||
@@ -22,7 +21,6 @@ export type EntityPolySeq = Table<mmCIF_Schema['entity_poly_seq']>
|
||||
export type EntityBranch = Table<mmCIF_Schema['pdbx_entity_branch']>
|
||||
export type ChemComp = Table<mmCIF_Schema['chem_comp']>
|
||||
export type ChemCompIdentifier = Table<mmCIF_Schema['pdbx_chem_comp_identifier']>
|
||||
export type StructModResidue = Table<mmCIF_Schema['pdbx_struct_mod_residue']>
|
||||
export type AtomSite = Table<mmCIF_Schema['atom_site']>
|
||||
export type IhmSphereObjSite = Table<mmCIF_Schema['ihm_sphere_obj_site']>
|
||||
export type IhmGaussianObjSite =Table<mmCIF_Schema['ihm_gaussian_obj_site']>
|
||||
@@ -41,7 +39,6 @@ export const BasicSchema = {
|
||||
pdbx_entity_branch: mmCIF_Schema.pdbx_entity_branch,
|
||||
chem_comp: mmCIF_Schema.chem_comp,
|
||||
pdbx_chem_comp_identifier: mmCIF_Schema.pdbx_chem_comp_identifier,
|
||||
pdbx_struct_mod_residue: mmCIF_Schema.pdbx_struct_mod_residue,
|
||||
atom_site: mmCIF_Schema.atom_site,
|
||||
ihm_sphere_obj_site: mmCIF_Schema.ihm_sphere_obj_site,
|
||||
ihm_gaussian_obj_site: mmCIF_Schema.ihm_gaussian_obj_site,
|
||||
@@ -61,7 +58,6 @@ export interface BasicData {
|
||||
pdbx_entity_branch: EntityBranch
|
||||
chem_comp: ChemComp
|
||||
pdbx_chem_comp_identifier: ChemCompIdentifier
|
||||
pdbx_struct_mod_residue: StructModResidue
|
||||
atom_site: AtomSite
|
||||
ihm_sphere_obj_site: IhmSphereObjSite
|
||||
ihm_gaussian_obj_site: IhmGaussianObjSite
|
||||
|
||||
@@ -13,9 +13,9 @@ import { Sequence } from '../../../mol-model/sequence';
|
||||
import { CoarseHierarchy } from '../../../mol-model/structure/model/properties/coarse';
|
||||
import { BasicData } from './schema';
|
||||
|
||||
export function getSequence(data: BasicData, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence {
|
||||
export function getSequence(data: BasicData, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy): StructureSequence {
|
||||
if (!data.entity_poly_seq || !data.entity_poly_seq._rowCount) {
|
||||
return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy, modResMap);
|
||||
return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy);
|
||||
}
|
||||
|
||||
const { entity_id, num, mon_id } = data.entity_poly_seq;
|
||||
@@ -37,7 +37,7 @@ export function getSequence(data: BasicData, entities: Entities, atomicHierarchy
|
||||
|
||||
byEntityKey[entityKey] = {
|
||||
entityId: id,
|
||||
sequence: Sequence.ofResidueNames(compId, seqId, modResMap)
|
||||
sequence: Sequence.ofResidueNames(compId, seqId)
|
||||
};
|
||||
|
||||
sequences.push(byEntityKey[entityKey]);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Table, Column } from '../../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { WaterNames } from '../../../mol-model/structure/model/types';
|
||||
import { WaterNames, PolymerNames } from '../../../mol-model/structure/model/types';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { BasicSchema } from '../basic/schema';
|
||||
|
||||
@@ -77,12 +77,14 @@ export class ComponentBuilder {
|
||||
private ids: string[] = []
|
||||
private names: string[] = []
|
||||
private types: mmCIF_Schema['chem_comp']['type']['T'][] = []
|
||||
private mon_nstd_flags: mmCIF_Schema['chem_comp']['mon_nstd_flag']['T'][] = []
|
||||
|
||||
private set(c: Component) {
|
||||
this.comps.set(c.id, c)
|
||||
this.ids.push(c.id)
|
||||
this.names.push(c.name)
|
||||
this.types.push(c.type)
|
||||
this.mon_nstd_flags.push(PolymerNames.has(c.id) ? 'y' : 'n')
|
||||
}
|
||||
|
||||
private getAtomIds(index: number) {
|
||||
@@ -141,6 +143,7 @@ export class ComponentBuilder {
|
||||
id: Column.ofStringArray(this.ids),
|
||||
name: Column.ofStringArray(this.names),
|
||||
type: Column.ofStringAliasArray(this.types),
|
||||
mon_nstd_flag: Column.ofStringAliasArray(this.mon_nstd_flags),
|
||||
}, this.ids.length)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ class FormatRegistry<T> {
|
||||
this.map.set(kind, obtain)
|
||||
}
|
||||
|
||||
remove(kind: ModelFormat['kind']) {
|
||||
this.map.delete(kind)
|
||||
}
|
||||
|
||||
get(kind: ModelFormat['kind']) {
|
||||
return this.map.get(kind)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Table } from '../../mol-data/db';
|
||||
import { AtomSiteAnisotrop } from './property/anisotropic';
|
||||
import { ComponentBond } from './property/bonds/comp';
|
||||
import { StructConn } from './property/bonds/struct_conn';
|
||||
import { ModelCrossLinkRestraint } from './property/pair-restraints/cross-links';
|
||||
|
||||
function modelSymmetryFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -65,14 +64,6 @@ function structConnFromMmcif(model: Model) {
|
||||
}
|
||||
StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif)
|
||||
|
||||
function crossLinkRestraintFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
const { ihm_cross_link_restraint } = model.sourceData.data.db;
|
||||
if (ihm_cross_link_restraint._rowCount === 0) return;
|
||||
return ModelCrossLinkRestraint.fromTable(ihm_cross_link_restraint, model)
|
||||
}
|
||||
ModelCrossLinkRestraint.Provider.formatRegistry.add('mmCIF', crossLinkRestraintFromMmcif)
|
||||
|
||||
//
|
||||
|
||||
export { MmcifFormat }
|
||||
|
||||
@@ -162,8 +162,8 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
}
|
||||
|
||||
const categories = {
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
entity: CifCategory.ofTable('entity', entityBuilder.getEntityTable()),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', componentBuilder.getChemCompTable()),
|
||||
atom_site: CifCategory.ofFields('atom_site', getAtomSite(atomSite)),
|
||||
atom_site_anisotrop: CifCategory.ofFields('atom_site_anisotrop', getAnisotropic(anisotropic))
|
||||
} as any;
|
||||
|
||||
@@ -117,7 +117,7 @@ function getAssemblyOperators(matrices: Matrices, operatorNames: string[][], sta
|
||||
Mat4.mul(m, m, matrices.get(op[i])!);
|
||||
}
|
||||
index++
|
||||
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { id: assemblyId, operList: op });
|
||||
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { assembly: { id: assemblyId, operId: index, operList: op } });
|
||||
}
|
||||
|
||||
return operators;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
// TODO
|
||||
// ihm_predicted_contact_restraint: {
|
||||
// id: int,
|
||||
// entity_id_1: str,
|
||||
// entity_id_2: str,
|
||||
// asym_id_1: str,
|
||||
// asym_id_2: str,
|
||||
// comp_id_1: str,
|
||||
// comp_id_2: str,
|
||||
// seq_id_1: int,
|
||||
// seq_id_2: int,
|
||||
// atom_id_1: str,
|
||||
// atom_id_2: str,
|
||||
// distance_upper_limit: float,
|
||||
// probability: float,
|
||||
// restraint_type: Aliased<'lower bound' | 'upper bound' | 'lower and upper bound'>(str),
|
||||
// model_granularity: Aliased<'by-residue' | 'by-feature' | 'by-atom'>(str),
|
||||
// dataset_list_id: int,
|
||||
// software_id: int,
|
||||
// },
|
||||
@@ -15,7 +15,7 @@ import { Loci } from '../../mol-model/loci';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { CustomModelProperty } from './custom-model-property';
|
||||
import { CustomProperty } from './custom-property';
|
||||
import { LociLabelProvider } from '../../mol-plugin/util/loci-label-manager';
|
||||
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
|
||||
|
||||
export { CustomElementProperty };
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace CustomElementProperty {
|
||||
})
|
||||
}
|
||||
|
||||
function createColorThemeProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getColor: (p: T) => Color, defaultColor: Color) {
|
||||
function createColorThemeProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getColor: (p: T) => Color, defaultColor: Color): ColorTheme.Provider<{}> {
|
||||
|
||||
function Coloring(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
|
||||
let color: LocationColor;
|
||||
@@ -98,11 +98,17 @@ namespace CustomElementProperty {
|
||||
}
|
||||
|
||||
return {
|
||||
name: modelProperty.descriptor.name,
|
||||
label: modelProperty.label,
|
||||
category: 'Custom',
|
||||
factory: Coloring,
|
||||
getParams: () => ({}),
|
||||
defaultValues: {},
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +116,7 @@ namespace CustomElementProperty {
|
||||
return function(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
const e = loci.elements[0];
|
||||
if (!e) return
|
||||
if (!e || !e.unit.model.customProperties.hasReference(modelProperty.descriptor)) return
|
||||
const data = modelProperty.get(e.unit.model).value
|
||||
const element = e.unit.elements[OrderedSet.start(e.indices)]
|
||||
const value = data?.get(element)
|
||||
|
||||
@@ -49,24 +49,28 @@ namespace CustomModelProperty {
|
||||
label: builder.label,
|
||||
descriptor: builder.descriptor,
|
||||
getParams: builder.getParams,
|
||||
defaultParams: builder.defaultParams,
|
||||
isApplicable: builder.isApplicable,
|
||||
attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}) => {
|
||||
attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
|
||||
if (addRef) data.customProperties.reference(builder.descriptor, true);
|
||||
const property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
const p = PD.merge(builder.defaultParams, property.props, props)
|
||||
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
|
||||
const value = await builder.obtain(ctx, data, p)
|
||||
data.customProperties.add(builder.descriptor);
|
||||
set(data, p, value);
|
||||
},
|
||||
ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add),
|
||||
get: (data: Model) => get(data)?.data,
|
||||
set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
|
||||
const property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
const p = PD.merge(builder.defaultParams, property.props, props)
|
||||
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
|
||||
// this invalidates property.value
|
||||
set(data, p, undefined)
|
||||
}
|
||||
}
|
||||
},
|
||||
props: (data: Model) => get(data).props,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user