Compare commits

...

457 Commits

Author SHA1 Message Date
Alexander Rose
73ac445a44 2.4.0 2021-11-25 14:46:10 -08:00
Alexander Rose
1a1d1d9d30 changelog 2021-11-25 14:41:19 -08:00
Alexander Rose
062aff76da update schemas 2021-11-25 14:40:35 -08:00
Alexander Rose
7d0d24b66d update packages 2021-11-25 14:40:26 -08:00
Alexander Rose
6655672d11 Merge pull request #290 from molstar/smoothing2
Smooth Overpaint and Transparency
2021-11-25 14:13:10 -08:00
Alexander Rose
6e573ae410 reduce args counts in geo exporters 2021-11-25 14:04:58 -08:00
Alexander Rose
1c48c02473 basic overpaint for direct-volume isosurface 2021-11-24 19:49:42 -08:00
Alexander Rose
78be3320ce geo export support smoothed overpaint/transparency 2021-11-24 19:49:05 -08:00
Alexander Rose
c8018800cc grid-based smoothing of Overpaint and Transparency 2021-11-24 19:47:07 -08:00
Alexander Rose
bb795aca98 refactor grid-based color smoothing
- support rgba and alpha values
- CPU and GPU versions
- for Mesh and TextureMesh
2021-11-24 19:43:15 -08:00
Alexander Rose
2cb1279f4c gl compute utils improvements
- CopyRenderable
- readTexture and readAlphaTexture helpers
2021-11-24 19:10:15 -08:00
Alexander Rose
b876c6f618 avoid unnecessary representation state updates 2021-11-24 18:51:16 -08:00
Alexander Rose
3a7dfc055e add Representation.geometryVersion
- increments whenever the geometry of any visual changes
2021-11-24 18:49:10 -08:00
Alexander Rose
928e521ac7 improve handling of .meta in Mesh & TextureMesh 2021-11-24 18:36:04 -08:00
Alexander Rose
e5e9598e4b changelog 2021-11-24 18:31:08 -08:00
Alexander Rose
e6e1809592 Fix secondary-structure property handling
- StructureElement.Property was incorrectly resolving type & key
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
2021-11-24 18:30:53 -08:00
Alexander Rose
812f97ddb7 skip picking/depth pass for volume rendering
- not supported in shader anyway
- was printing 'no output' warning in Chrome console
2021-11-24 18:20:42 -08:00
Alexander Rose
c6b814b31b re-enable VAO with better workaround 2021-11-24 18:17:48 -08:00
Alexander Rose
98566fa389 improve error handling
- console.error if not re-thrown
- better messages for users
2021-11-24 18:15:47 -08:00
David Sehnal
4318c89bdb Merge pull request #288 from jpattle/allow-v3-sdf
Added the ability to handle v3000 sd files
2021-11-23 15:49:42 +01:00
Jason Pattle
b41a97ce6a Added a separate function to handle v2 counts and refactored the existing code that distinguishes v2 and v3 atom and bond counts 2021-11-23 10:30:09 +00:00
Jason Pattle
862c384dc0 Added the ability to handle v3000 sd files. Added a set of utility functions for parsing atoms and bonds from v3000 sd files. Updated the existing sdf parser to determine the version and run the v3000 sd file parser functions instead of the default v2000 ones. Added tests to verify parsing functionality for example v3000 ctab 2021-11-22 16:00:35 +00:00
Alexander Rose
26cc7e94c2 2.3.9 2021-11-20 16:58:28 -08:00
Alexander Rose
c6fe6ddcba switch off VAO support for now 2021-11-20 16:54:38 -08:00
Alexander Rose
154984e74d 2.3.8 2021-11-20 16:30:22 -08:00
Alexander Rose
72fcaf8321 changelog 2021-11-20 16:24:54 -08:00
Alexander Rose
0c14ca5888 workaround for VAO issue in Chrome 96 2021-11-20 16:14:30 -08:00
Alexander Rose
a85ede5058 fix unused vertex attribute handling 2021-11-20 16:13:18 -08:00
Alexander Rose
db49a16184 fix double canvas context creation 2021-11-20 12:52:47 -08:00
dsehnal
0704db2343 replace webpack-version-file-plugin 2021-11-20 13:45:28 +01:00
dsehnal
425dca4665 fix sass division 2021-11-20 12:55:46 +01:00
dsehnal
8d65ccabd2 update packages
- use sass instead of node sass
2021-11-20 12:49:26 +01:00
Alexander Rose
cbd417ca13 2.3.7 2021-11-15 19:31:07 -08:00
Alexander Rose
1578211157 add helix profile option to cartoon repr 2021-11-13 13:16:11 -08:00
Alexander Rose
15932dc5df handle parent in Structure.remapModel 2021-11-13 09:23:57 -08:00
dsehnal
7db2205956 changelog 2021-11-09 21:18:55 +01:00
dsehnal
d87f0d236a ViewerOptions.collapseRightPanel 2021-11-09 21:17:01 +01:00
dsehnal
16daca6008 undo test code 2021-11-09 21:13:28 +01:00
dsehnal
a0d919c8db Viewer.loadTrajectory 2021-11-09 21:12:24 +01:00
David Sehnal
ffee2bf1c4 Merge pull request #285 from MadCatX/tweak-measurement-order-labels
Tweak measurement order labels
2021-11-09 10:28:13 +01:00
Michal Malý
de77f6ac59 Fix a visual glitch where the label border was initially rendered with
half of the intended size.
2021-11-09 09:59:25 +01:00
Michal Malý
c8c2ebcd65 Reduce Z-offset of measurement order labels to avoid too early clipping 2021-11-09 09:59:25 +01:00
Michal Malý
42796b984f Reduce border width of measurement order labels 2021-11-09 09:58:58 +01:00
David Sehnal
746557bf52 Merge pull request #284 from russellp17/master
Move jest types to devDependencies
2021-11-08 22:48:15 +01:00
Russell Parker
a5020a9e96 Move jest types to devDependencies 2021-11-08 15:58:14 -05:00
dsehnal
2ebb0a35fd 2.3.6 2021-11-08 18:57:06 +01:00
dsehnal
64aaa92d45 changelog 2021-11-08 18:54:52 +01:00
dsehnal
4baf391efe prefer webgl1 in safari 15.1
- WebGL2 is broken there for Mol* shaders
- It works again in Safari 15.4 tech preview
2021-11-08 18:50:16 +01:00
David Sehnal
5afdcff6a5 Merge pull request #273 from molstar/shader-tests
add basic unit tests for graphics shaders
2021-11-08 18:40:51 +01:00
dsehnal
339c397860 package lock 2021-11-08 18:30:52 +01:00
dsehnal
a58cbd31ef Merge branch 'master' of https://github.com/molstar/molstar into shader-tests 2021-11-08 18:30:30 +01:00
David Sehnal
d232b01cf9 Merge pull request #283 from MadCatX/improve-measurements-ux
Improve measurements user experience
2021-11-08 17:12:37 +01:00
Michal Malý
ec95270854 Use the entire element to trigger highlighting of loci from additionsHistory 2021-11-08 15:46:07 +01:00
Michal Malý
78cc0d960f Show the order of locis to be used for measurements in 3D view 2021-11-08 15:46:02 +01:00
Alexander Rose
7f39cf0f37 add missing updateFocusRepr to atomicDetail preset
- fixes #280
2021-11-02 22:10:35 -07:00
Alexander Rose
4592510a95 linting fix 2021-10-30 21:50:15 -07:00
Alexander Rose
46d5442dc5 Merge pull request #252 from corredD/forkdev
binary model loading support and latest mycoplasma model.
2021-10-30 17:11:27 -07:00
Alexander Rose
271cff4aba changelog 2021-10-30 17:09:35 -07:00
Alexander Rose
94fd5a97d6 Merge branch 'master' of https://github.com/molstar/molstar into pr/corredD/252 2021-10-30 16:53:35 -07:00
Alexander Rose
28678e2f80 Merge pull request #270 from molstar/measurements
Additional measurement controls
2021-10-30 16:52:32 -07:00
Alexander Rose
406307a432 add radiusScale param to orientation measurement 2021-10-30 16:48:47 -07:00
Alexander Rose
56345b5096 Merge branch 'master' of https://github.com/molstar/molstar into measurements 2021-10-30 16:31:12 -07:00
Alexander Rose
3fcc42ee0e fix, proper EmptyRepresentationProvider 2021-10-30 16:26:37 -07:00
Alexander Rose
b903677f8a gh action, add npm run build 2021-10-30 16:12:42 -07:00
Alexander Rose
ef4b632a07 update package-lock 2021-10-30 16:05:24 -07:00
Alexander Rose
e9d485ca85 gh action, use npm ci 2021-10-30 16:01:09 -07:00
Alexander Rose
a149fa5929 gh action 2021-10-30 15:53:58 -07:00
Alexander Rose
bb3dde585b gh action 2021-10-30 15:46:23 -07:00
Alexander Rose
cd6bbeaa86 gh action 2021-10-30 15:44:06 -07:00
Alexander Rose
e3d24dae4b gh action 2021-10-30 15:43:05 -07:00
Alexander Rose
687a814a62 gh action
- update eslint
- add jest
2021-10-30 15:02:25 -07:00
Alexander Rose
8f2e99dc51 improve shader tests
- made gl package optional
- skip test if gl package not available
2021-10-30 14:52:11 -07:00
Alexander Rose
568be030c3 Merge branch 'master' of https://github.com/molstar/molstar into shader-tests 2021-10-30 14:45:19 -07:00
Alexander Rose
97c3ab8b5a changelog 2021-10-30 14:44:27 -07:00
Alexander Rose
5db646d139 fix marker highlight color overriding select color 2021-10-30 14:43:57 -07:00
Alexander Rose
340f8f1af3 add additional aromatic bond visual params 2021-10-30 14:42:17 -07:00
dsehnal
4484a4452c 2.3.5 2021-10-19 18:22:50 +02:00
dsehnal
65b654a0a2 fix index pair bonds order assignment 2021-10-19 18:20:54 +02:00
dsehnal
a8e0c13b0e fix sequence viewer for PDB files with COMPND record and multichain entities 2021-10-17 13:21:57 +02:00
dsehnal
41d67eb642 2.3.4 2021-10-12 19:05:48 +02:00
dsehnal
c76c8335d1 changelog 2021-10-12 19:03:37 +02:00
dsehnal
42528b7be5 fix argparse config 2021-10-12 19:01:23 +02:00
Alexander Rose
b371f8c11c Merge branch 'master' of https://github.com/molstar/molstar into shader-tests 2021-10-03 15:24:02 -07:00
Alexander Rose
3d651b40f0 use node 14 in lint action 2021-10-03 15:12:58 -07:00
Alexander Rose
895a13fc0d Merge branch 'master' into shader-tests 2021-10-03 14:36:23 -07:00
Alexander Rose
c94acff82e update packages 2021-10-03 11:12:24 -07:00
Alexander Rose
2f9ac711d1 add basic unit tests for graphics shaders
- compile using `gl` package
2021-10-03 10:52:58 -07:00
Alexander Rose
93b9953f6d add missing createEmpty* geo utils
- direct-volume
- image
- texture-mesh
2021-10-03 10:43:56 -07:00
Alexander Rose
dcaf6f8927 add multipleBonds param to bond visuals 2021-10-02 15:44:17 -07:00
Alexander Rose
d96eb404e1 add elements crosses visual 2021-10-02 15:09:13 -07:00
Alexander Rose
07322819f0 Merge pull request #271 from molstar/pick-atom
Picking improvements
2021-10-02 13:41:25 -07:00
Alexander Rose
9be686686d add pickPadding config option 2021-10-02 09:34:56 -07:00
Alexander Rose
3df539c9e1 Merge branch 'master' of https://github.com/molstar/molstar into pick-atom 2021-10-02 09:30:07 -07:00
dsehnal
903f06bab6 2.3.3 2021-10-01 17:56:26 +02:00
dsehnal
13f2810f90 fix direct volume shader 2021-10-01 17:54:33 +02:00
dsehnal
ee8cae16d2 2.3.2 2021-10-01 17:12:42 +02:00
dsehnal
feaf6f7fd4 (temporarily) prefer webgl1 on iOS 2021-10-01 17:10:49 +02:00
dsehnal
04775a2e44 2.3.1 2021-09-28 16:13:51 +02:00
dsehnal
bec9fec755 chem_comp_bond and struct_conn to mmcif exporter
+ fix argparse config for Model/Volume servers
2021-09-28 16:12:01 +02:00
Alexander Rose
e840059a38 pick improvements
- ensure lines & points are at least 1 pixel big
- look around center pixel in a spiral for hits
2021-09-26 17:20:16 -07:00
Alexander Rose
1bd0339dec add points visual to line repr 2021-09-26 17:13:49 -07:00
Alexander Rose
d0eaf2f71e Merge branch 'master' of https://github.com/molstar/molstar into pick-atom 2021-09-26 13:41:23 -07:00
Alexander Rose
c7edf40afe add markerPriority param to renderer 2021-09-26 13:31:41 -07:00
Alexander Rose
b44962eb2f lint: add semi-spacing rule 2021-09-26 13:14:32 -07:00
Alexander Rose
254c9efbf5 improve/fix implicit atom picking 2021-09-26 13:06:56 -07:00
Alexander Rose
73e9aed98c add preferAtoms param to Select/Highlight behaviors 2021-09-26 12:58:29 -07:00
Alexander Rose
6e60d9713a fix bond atoms not added in selection manager 2021-09-26 12:53:53 -07:00
Alexander Rose
ef0593b1e2 add pixel-scale & pick-scale GET params to Viewer 2021-09-26 12:43:57 -07:00
Alexander Rose
7831fa8b33 fix: pickScale not considered in line/point shader 2021-09-26 12:39:44 -07:00
dsehnal
c64851492c applyMarkerAction take 2 2021-09-21 18:38:05 +02:00
dsehnal
4a2e93e265 fix applyMarkerAction edge case 2021-09-21 10:15:29 +02:00
Alexander Rose
d4bb1a6e93 wip: prefer to pick atoms close to ends over bonds 2021-09-19 16:24:45 -07:00
Alexander Rose
55de0aba69 fix point repr & shader 2021-09-19 16:00:40 -07:00
Alexander Rose
ecfa7b5a99 fix currentTheme not set in Representation.createMulti 2021-09-19 15:45:08 -07:00
dsehnal
bee3dc4595 fix double bonds from sctruct_conn records 2021-09-19 17:40:38 +02:00
Alexander Rose
787ca47825 fix line shader not accounting for aspect ratio
- also remove uViewportHeight in favor of uViewport
2021-09-18 23:13:00 -07:00
Alexander Rose
b06c134b61 add additional measurement controls
- orientation (box, axes, ellipsoid)
- plane (best fit)
2021-09-18 22:39:40 -07:00
Alexander Rose
3436d03468 add helpers to work with many locis
- StructureElement.Loci.getPrincipalAxesMany
- structureElementLociLabelMany
2021-09-18 22:13:22 -07:00
Alexander Rose
58df6f3b85 formating 2021-09-18 22:12:08 -07:00
Alexander Rose
6fab6ce1f2 add map-provider GET param to Viewer app 2021-09-18 16:52:35 -07:00
Alexander Rose
9fd95f1a11 handle missing occupancy column
- treat as occ 1
2021-09-18 16:47:52 -07:00
Alexander Rose
69fe0901e2 add CharmmSaccharideNames 2021-09-18 16:37:39 -07:00
Alexander Rose
ffaf008dce limit max display counts in sequence panel
- MaxSelectOptionsCount
- MaxSequenceWrappersCount
- workaround for cellpack models
2021-09-18 16:30:14 -07:00
ludovic autin
eb196a41b5 change variables names to avoid confusion with other types. Added the xrayshading for the compartment geometry 2021-09-14 11:25:15 -07:00
ludovic autin
35baaaf594 support for compartment PLY file. 2021-09-13 11:46:27 -07:00
Alexander Rose
0fc305aaea fix linting issues 2021-09-12 23:32:55 -07:00
Alexander Rose
bf67546a61 Merge branch 'master' of https://github.com/molstar/molstar into pr/corredD/252 2021-09-12 23:30:26 -07:00
Alexander Rose
8c417ef35c lint: add space-before-blocks rule 2021-09-12 23:23:03 -07:00
Alexander Rose
01691f3050 lint: add keyword-spacing rule 2021-09-12 23:20:59 -07:00
Alexander Rose
128abf3090 lint: add no-multi-spaces rule 2021-09-12 23:13:44 -07:00
Alexander Rose
0c0e995256 lint: add no-multi-spaces rule 2021-09-12 23:11:29 -07:00
Alexander Rose
4090498f92 lint: add func-call-spacing rule 2021-09-12 23:03:35 -07:00
Alexander Rose
24677d6931 lint: add space-before-function-paren rule 2021-09-12 22:57:48 -07:00
Alexander Rose
80d54afdd0 Merge branch 'master' into forkdev 2021-09-12 20:16:00 -07:00
Alexander Rose
908fff8041 lint: add prefer-const rule 2021-09-12 20:02:53 -07:00
Alexander Rose
aa1eb90f66 lint: add computed-property-spacing rule 2021-09-12 19:41:13 -07:00
Alexander Rose
1d21787e7e lint: add space-in-parens rule 2021-09-12 19:37:26 -07:00
Alexander Rose
42409a2bc7 lint: add array-bracket-spacing rule 2021-09-12 19:32:54 -07:00
Alexander Rose
b31302ba3a lint: add object-curly-spacing rule 2021-09-12 18:45:32 -07:00
Alexander Rose
3840b0041b lint: add key-spacing rule 2021-09-12 18:38:45 -07:00
Alexander Rose
cc68f8311d lint: add no-throw-literal rule 2021-09-12 16:22:43 -07:00
Alexander Rose
3400c8e94a update coreCif dictionary 2021-09-12 16:19:23 -07:00
Alexander Rose
61c47c517b update mmcif schema types and derived data 2021-09-12 15:06:54 -07:00
Alexander Rose
76674917ca update rscb graphql schema types 2021-09-12 15:04:49 -07:00
Alexander Rose
b835eb8de7 update packages 2021-09-12 13:39:00 -07:00
Alexander Rose
2a74dfcf46 factor out membrane sphere handling 2021-09-12 10:26:10 -07:00
Alexander Rose
b0f447adde fix cellpack results file asset handling 2021-09-12 10:17:37 -07:00
Alexander Rose
3f0d476e94 Merge pull request #266 from JonStargaryen/master
ANVIL: add TMDET amino acid classification and reference
2021-09-10 22:41:01 -07:00
Sebastian Bittrich
9423d56d36 lint 2021-09-09 16:16:49 -07:00
Sebastian Bittrich
0f03fe99d6 ANVIL: fix for 6y1z 2021-09-09 16:02:39 -07:00
Sebastian Bittrich
556d5bb003 ANVIL: add TMDET ref and option 2021-09-09 09:06:51 -07:00
dsehnal
8e3ea6943f updated packages & tsc 2021-09-07 14:10:10 +02:00
Alexander Rose
599cfc1b1c 2.3.0 2021-09-06 15:33:03 -07:00
Alexander Rose
84eccb5019 changelog 2021-09-06 15:29:44 -07:00
Alexander Rose
2dd07bb0e3 improved getMarkersAverage performance
- use lut
- add performance tests
2021-09-04 15:12:35 -07:00
Alexander Rose
f404d23280 take include/exclude flags into account when displaying aromatic bonds
- this allows to show aromatic rings only when explicitely given
2021-09-04 14:56:40 -07:00
Alexander Rose
8b7333b470 avoid unnecessary draw calls/ui updates when marking
- fix wrong type narrowing of Loci.isEmpty
2021-09-04 14:48:09 -07:00
ludovic autin
b8628ccff1 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-09-01 09:55:13 -07:00
Alexander Rose
7d26567d40 Merge pull request #258 from molstar/marking
Add optional marking pass
2021-08-31 21:18:23 -07:00
Alexander Rose
8f6dbf2192 asorted performance tweaks 2021-08-31 21:14:02 -07:00
Alexander Rose
db350ddfd3 loci/marking performance improvements
- use Interval for ranges instead of SortedArray
- pre-check if loci overlaps with unit visual
2021-08-30 22:43:01 -07:00
Alexander Rose
c0144d826c SortedArray: add .isRange, improve .areEqual 2021-08-30 22:33:09 -07:00
Alexander Rose
de3e819b80 fix uMarker not being updated 2021-08-30 22:29:35 -07:00
Alexander Rose
bbf96567b1 handle clipping/fog for marking edges 2021-08-29 22:14:11 -07:00
Alexander Rose
c9a3254bd6 fix partial marker average calculation 2021-08-29 21:25:41 -07:00
Alexander Rose
bad6d030f1 marker-data improvements
- add uniform marker type
- check if a loci is superset of a visual
- special case to improve reversing the previous mark
2021-08-29 10:44:09 -07:00
Alexander Rose
e1ad67a059 StructureElement.Loci improvements
- early return in .isEmpty
- ensure Interval is used when possible in .extendToAllInstances
2021-08-29 10:35:05 -07:00
ludovic autin
4c30057edf I am trying to get the binary asset to get cached. But still can't save a working session. 2021-08-27 16:14:43 -07:00
ludovic autin
53a4826274 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-08-27 14:55:22 -07:00
dsehnal
8d3ac92989 2.2.3 2021-08-25 17:40:28 +02:00
dsehnal
d6eb334d12 changelog 2021-08-25 17:38:28 +02:00
Alexander Rose
c62f19623c add optional marking pass
- outlines visible and hidden parts of highlighted/selected groups
- add highlightStrength/selectStrength renderer params
2021-08-22 12:08:15 -07:00
Alexander Rose
77139afe7f tweak Interval.size 2021-08-21 14:00:51 -07:00
Alexander Rose
54476ad85e improve print texture debug helpers 2021-08-21 13:59:38 -07:00
Alexander Rose
6dd876232d avoid superfluous calls to Loci.isWholeStructure 2021-08-21 13:51:40 -07:00
Alexander Rose
ae2314d76c fix camera/bounding helper not showing up 2021-08-21 13:48:56 -07:00
David Sehnal
6667509745 Merge pull request #257 from JonStargaryen/master
ANVIL: improve prediction for 3pqr
2021-08-21 20:22:38 +02:00
JonStargaryen
5c871a5aae ANVIL: increase number of sphere points to 175 2021-08-20 12:43:24 -07:00
ludovic autin
393fc99ed2 moved the model loading in model.ts and get the info from the recipe dictionary ( result file and lipids file ) if available. Fix the dates and some const / let variable defintion 2021-08-16 12:35:51 -07:00
ludovic autin
7c5dff1c8b Merge remote-tracking branch 'upstream/master' into forkdev 2021-08-16 11:55:09 -07:00
Alexander Rose
2482ef92af Improved StructureElement.Loci.size performance
- inlined code
- important for marking large cellpack models
2021-08-15 14:42:49 -07:00
Alexander Rose
db59303a84 Merge pull request #244 from molstar/meshproc
Mesh processing: border smoothing
2021-08-14 19:36:23 -07:00
Alexander Rose
fe700953ff Merge branch 'master' into meshproc 2021-08-14 19:32:06 -07:00
Alexander Rose
047946e41c remove superfluous type casts 2021-08-14 19:30:16 -07:00
ludovic autin
ce3f13431d endianess test with IsNativeEndianLittle 2021-08-13 10:58:25 -07:00
ludovic autin
df54766ab2 lower-case icosahedron 2021-08-12 10:48:27 -07:00
ludovic autin
94233fbcd9 eslint error fix 2021-08-11 11:07:34 -07:00
ludovic autin
d044496eaa fix eslint errors 2021-08-11 11:04:08 -07:00
ludovic autin
ca825d720e binary model loading support, latest mycoplasma model. 2021-08-11 10:44:44 -07:00
dsehnal
f833efae37 2.2.2 2021-08-11 14:54:03 +02:00
dsehnal
be4b787e66 Fix mol-script query compiler const expression recognition 2021-08-11 14:49:52 +02:00
David Sehnal
950b1c179a Merge pull request #248 from MadCatX/fix-isosurface
Do not cache LevelTexturesFramebuffers as they may become invalid
2021-08-10 12:40:52 +02:00
Michal Malý
2bd1a01afb Do not attach framebuffer unnecessarily 2021-08-10 09:15:05 +02:00
dsehnal
2fe43eda2b Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-08-09 20:33:57 +02:00
dsehnal
45fc0c61af Mesh.smoothEdges options 2021-08-09 20:32:40 +02:00
dsehnal
7e7993f5ba improve fillEdges in mesh edge smoothing
- sort boundary vertices
- limit the length added edges
2021-08-09 20:22:13 +02:00
Michal Malý
9d34dbff0f Do not cache LevelTexturesFramebuffers as they may become invalid 2021-08-09 09:58:55 +02:00
Alexander Rose
ba1b03f01b fix TransformData issues, see #133
- handle structure vs structure.root in ExplodeStructureRepresentation3D and SpinStructureRepresentation3D
2021-08-08 13:11:50 -07:00
Alexander Rose
791f7ca3c8 Merge pull request #245 from sukolsak/optimize-setCylinderMat 2021-08-08 10:45:23 -07:00
Sukolsak Sakshuwong
c14d50e4ff optimize setCylinderMat() 2021-08-08 08:51:16 -07:00
Alexander Rose
3e9de449c8 cleanup 2021-08-07 19:45:47 -07:00
Alexander Rose
0132c7ef5e Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-08-07 19:42:41 -07:00
Alexander Rose
aa2222c086 remove clipSphere option & param cleanup
- clip objects are better
- includeParent option not useful for gaussian-surface visuals
2021-08-07 19:20:51 -07:00
dsehnal
fc2765d376 2.2.1 2021-08-02 18:11:24 +02:00
dsehnal
9d85194082 2.2.1 changelog 2021-08-02 18:09:50 +02:00
David Sehnal
abfcc60898 Merge pull request #243 from molstar/input-observer-improvements
Input observer improvements
2021-08-02 18:08:48 +02:00
dsehnal
c688a83fa2 fix typo 2021-08-02 18:07:34 +02:00
dsehnal
77376056b9 changelog 2021-08-02 18:03:07 +02:00
dsehnal
8efd943c2b PinchInput.fractionDelta 2021-08-02 17:24:49 +02:00
dsehnal
b230655439 fix type 2021-08-02 16:55:06 +02:00
dsehnal
8ba792c4b0 add maxWheelDelta 2021-08-02 16:50:09 +02:00
dsehnal
fde8ca69e4 support for Safari gestures (pinch zoom on MacBook trackpad) 2021-08-02 16:30:48 +02:00
dsehnal
9ee1439299 normalize wheel speed in input observer 2021-08-02 14:45:25 +02:00
David Sehnal
195668760e Merge pull request #242 from sukolsak/export-overpaint
Add overpaint support to geometry exporters
2021-08-02 13:04:09 +02:00
Sukolsak Sakshuwong
bd64f1db9a update changelog and fix type 2021-08-02 01:19:31 -07:00
Sukolsak Sakshuwong
38a5a857aa refactor color calculation 2021-08-01 23:09:30 -07:00
Sukolsak Sakshuwong
5e8cdfe3a7 add overpaint support to geometry exporters 2021-08-01 22:30:05 -07:00
Alexander Rose
f892917e1c Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-08-01 14:04:50 -07:00
Alexander Rose
738b7f4ca5 Merge pull request #238 from molstar/dynbonds
Bond improvements (mostly IndexPairBonds)
2021-08-01 13:53:42 -07:00
Alexander Rose
bce53d03a5 bond tweaks
- add DefaultBondMaxRadius constant
- add IndexPairBonds.Props object
2021-08-01 13:49:44 -07:00
Alexander Rose
74f721ab9f improve Structure.asParent
- handle parent coordinate system not identity
2021-08-01 13:48:26 -07:00
Alexander Rose
f011025f16 changelog 2021-07-31 23:22:11 -07:00
Alexander Rose
9e44cd83fa add clipSphere param to molecular-surface mesh 2021-07-31 23:21:57 -07:00
Alexander Rose
e0e45b64ac optimize invertCantorPairing 2021-07-31 23:15:33 -07:00
Alexander Rose
f10b152252 Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-07-31 14:42:18 -07:00
Alexander Rose
23cf5c2fdd changelog and docs 2021-07-31 14:22:45 -07:00
Alexander Rose
e211abd5ae Merge branch 'master' of https://github.com/molstar/molstar into dynbonds 2021-07-31 14:11:45 -07:00
Alexander Rose
7199be4d62 tweak getColorSmoothingProps
- make independent of webgl context
2021-07-31 14:10:50 -07:00
Alexander Rose
e1a40ded1d add surronding atoms structure selection query 2021-07-31 13:53:49 -07:00
Alexander Rose
8999c3097d handle dynamicBonds in root structure helper 2021-07-31 13:50:07 -07:00
Alexander Rose
44308fa1fd add maxDistance prop to IndexPairBonds 2021-07-31 13:48:10 -07:00
Alexander Rose
f5dd2f4579 support coordinateSystem in structure.asParent 2021-07-31 13:45:39 -07:00
dsehnal
104999b7dc 2.2.0 2021-07-31 15:15:09 +02:00
dsehnal
e5341623d3 changelog v2.2.0 2021-07-31 15:12:40 +02:00
dsehnal
0e9238e5ec Canvas3D tweaks:
- update "forceDraw" logic
- Ensure the scene is re-rendered when viewport size changes
- Support noDraw mode in PluginAnimationLoop
2021-07-31 15:06:58 +02:00
dsehnal
43c292e2df Support new EMDB API for EM volume contour levels 2021-07-31 14:12:33 +02:00
dsehnal
fbfd1b20d8 Prefer _label_seq_id fields in secondary structure assignment 2021-07-31 13:54:11 +02:00
dsehnal
5330df87e1 Merge branch 'master' of https://github.com/molstar/molstar 2021-07-27 12:29:17 +02:00
dsehnal
ad6b3c6fe0 add DS_store to .gitignore 2021-07-27 12:28:56 +02:00
Alexander Rose
b983df7eb5 mesh edge smoothing 2021-07-25 20:26:17 -07:00
Alexander Rose
add76a87d9 remove unnecessary check
- see 7686b61728
2021-07-25 20:22:00 -07:00
Alexander Rose
f9f8350d28 dynamic pair bonds on coordinate changes
- add dynamicBonds structure parameter
- add maxRadius, ignoreWater bond compute parameters
- ensure inter unit bond visuals are recreated
2021-07-24 17:16:59 -07:00
Alexander Rose
b71c2f365c add operator Loci granularity 2021-07-24 16:55:07 -07:00
Alexander Rose
a5443189d3 missing param 2021-07-24 16:46:30 -07:00
Alexander Rose
7686b61728 fix includeParent for multi instance bond visuals 2021-07-24 16:31:26 -07:00
Alexander Rose
844c13cd35 2.2.0-dev.1 2021-07-20 21:08:51 -07:00
Alexander Rose
d1c8b92fdf Merge pull request #224 from sukolsak/usdz-export
USDZ export
2021-07-18 09:28:27 -07:00
Sukolsak Sakshuwong
93d33bca80 view USDZ in AR on iOS 2021-07-17 21:41:26 -07:00
Sukolsak Sakshuwong
6550e53414 Merge branch 'master' into usdz-export 2021-07-17 18:50:43 -07:00
Alexander Rose
96dddb0998 updated changelog 2021-07-17 10:54:34 -07:00
Alexander Rose
baa64d8109 handle mononucleotides when guessing component type 2021-07-17 10:42:56 -07:00
David Sehnal
2df145aa8f Merge pull request #231 from molstar/sdf-parser-improvements
Sdf parser improvements
2021-07-16 18:27:16 +02:00
dsehnal
06b9c5f2de change SDF data header parsing
- do not trim <> around field
- store whole line staring with '> ' as data header (without the staring '> ')
2021-07-16 18:25:22 +02:00
dsehnal
e03b689f27 add SdfFormat 2021-07-16 18:04:25 +02:00
Sukolsak Sakshuwong
e4cdcff3ee Merge branch 'master' into usdz-export 2021-07-12 00:11:24 -07:00
Alexander Rose
f73150d074 Merge pull request #225 from molstar/tubular-helices
Add tubularHelices parameter to Cartoon representation
2021-07-11 11:45:38 -07:00
Alexander Rose
451dc12689 cleanup 2021-07-11 11:31:02 -07:00
Alexander Rose
a3fb7762d8 add tubular helices to Cartoon representation 2021-07-10 15:41:54 -07:00
Alexander Rose
3dfafc3202 handle cell angles close to zero in dcd reader 2021-07-10 15:35:53 -07:00
Alexander Rose
4fcea991d3 set default outline scale back to 1 2021-07-10 15:28:49 -07:00
Alexander Rose
0607ed46d1 handle more common ff residue/atom names 2021-07-10 15:28:23 -07:00
Sukolsak Sakshuwong
30d6244e82 add support for USDZ 2021-07-10 05:19:42 -07:00
Sukolsak Sakshuwong
fab8c74365 move quantizeColors to MeshExporter 2021-07-09 13:10:09 -07:00
Sukolsak Sakshuwong
1952922e4e make RenderObjectExporter.getData async 2021-07-09 13:10:09 -07:00
Alexander Rose
1eb351369e 2.1.0 2021-07-05 16:11:46 -07:00
Alexander Rose
701d782485 changelog 2021-07-05 16:07:30 -07:00
Alexander Rose
dc8457c4dc smoother trace normals 2021-07-05 15:59:28 -07:00
Alexander Rose
f104cd4d11 add missing import 2021-07-05 13:47:39 -07:00
Alexander Rose
9b56a6ae65 Merge pull request #221 from molstar/aromatic
Aromatic bond display option
2021-07-05 13:42:11 -07:00
Alexander Rose
2485ad5a2f Merge branch 'master' into aromatic 2021-07-05 13:38:05 -07:00
Alexander Rose
a56716ab6a Merge pull request #222 from molstar/backbone
Backbone representation
2021-07-05 13:37:24 -07:00
Alexander Rose
e0aaaa989e Merge branch 'master' into backbone 2021-07-05 13:34:40 -07:00
Alexander Rose
9ec0f9e736 outline fixes and improvements
- better handle outlines in orthographic mode
- remove unused code
- increase default outline scale to 2
2021-07-05 13:31:00 -07:00
Alexander Rose
47968eeeec fix traceOnly handling in makeElementIgnoreTest 2021-07-04 14:54:52 -07:00
Alexander Rose
9c157b70e1 warning for arrayAreIntersecting/arrayIntersectionSize 2021-07-04 14:50:58 -07:00
Alexander Rose
6d7e4ca227 remove unused import 2021-07-04 14:48:06 -07:00
Alexander Rose
fccd08d2ec add backbone repr
- atomistic and coarse units
2021-07-04 14:39:56 -07:00
Alexander Rose
19bae202d0 handle Vec3.angle edge case 2021-07-04 14:35:46 -07:00
Alexander Rose
4ba0ae24e4 support aromatic bond display as dashes
- add arrayAreIntersecting/arrayIntersectionSize helpers
- prefer reference atoms within rings (also for double/triple bonds)
2021-07-03 23:12:06 -07:00
Alexander Rose
fcf3718d75 better parsing of mol2 bond types 2021-07-03 22:38:29 -07:00
Alexander Rose
df1dd94f1c fix BondType.Names 2021-07-03 22:35:35 -07:00
Alexander Rose
65ba401850 fix repr update for Structure.asParent objects 2021-07-03 22:26:20 -07:00
Alexander Rose
a98f5e1047 fix fxaa antialiasing
- was broken when used with other postprocessing effects
- expose texture.filter
2021-07-03 22:19:51 -07:00
David Sehnal
e5cf97d1ea Merge pull request #217 from sukolsak/fix-cylinder
Fix cylinder orientation
2021-06-28 14:45:38 +02:00
Sukolsak Sakshuwong
1844fc14b2 fix cylinder orientation 2021-06-27 13:14:30 -07:00
dsehnal
d185c0ef34 2.0.7 2021-06-23 12:44:20 +02:00
dsehnal
40a4211e75 fix CIF text encoder edge cases & added test 2021-06-23 12:41:23 +02:00
dsehnal
daa2bbd042 Merge branch 'master' of https://github.com/molstar/molstar 2021-06-21 16:39:25 +02:00
Alexander Rose
ed5b4b27a8 guard against atom_site not being available 2021-06-20 22:55:25 -07:00
Alexander Rose
408ccb4353 fix bond cylinder imposter update issue 2021-06-20 13:54:15 -07:00
Alexander Rose
99e3cd6654 fix image export issues
- handle pre-multiplied alpha
- don't clear draw target unless written to
2021-06-20 13:51:58 -07:00
Alexander Rose
0819ace1dc use CustomProperty.Provider.ref 2021-06-20 13:49:42 -07:00
dsehnal
987c9210bd In-place reordering support for Frame.x/y/z 2021-06-19 12:26:42 +02:00
dsehnal
84fb42a161 Support volumeIndex in Viewer.loadVolumeFromUrl 2021-06-19 11:24:32 +02:00
dsehnal
53d3480701 fix isConnectedTo query 2021-06-15 17:53:40 +02:00
David Sehnal
eb629ef337 Merge pull request #212 from sukolsak/center-export
Geometry export: center exported models
2021-06-14 15:55:16 +02:00
Sukolsak Sakshuwong
c26111e8fb center exported models 2021-06-13 23:32:00 -07:00
dsehnal
4853ff7a1a fix volume streaming channel visibility 2021-06-11 15:19:04 +02:00
dsehnal
1bdebda136 2.0.6 2021-06-01 18:49:51 +02:00
dsehnal
fe5b847797 2.0.6 changelog 2021-06-01 18:47:51 +02:00
dsehnal
19ec5b226c changelog 2021-06-01 12:43:11 +02:00
dsehnal
4bb32d31dc support atom id list in selection helper 2021-06-01 12:41:04 +02:00
Alexander Rose
976a469cc7 Merge pull request #199 from molstar/original-mesh-data
Better handling of processed meshes
2021-05-31 10:46:35 -07:00
Alexander Rose
86087aa3ca Merge pull request #203 from sukolsak/export-original-mesh-data
Geometry export: use original mesh data
2021-05-31 10:34:22 -07:00
Sukolsak Sakshuwong
c0e955d472 export original mesh data 2021-05-30 06:25:43 -07:00
Alexander Rose
eca052e52e fix entity-source color showing black
- fixes #172
- now always using light-grey/white for regions with unkown source
2021-05-29 15:20:27 -07:00
Alexander Rose
a1e05387e4 add Mesh.getOriginalData accessor 2021-05-29 15:01:02 -07:00
Alexander Rose
301940c8bd fix canvas not cleared
- #201
- happens e.g. with antialiasing disabled plus transparent background on
2021-05-29 14:26:11 -07:00
Alexander Rose
d96303627c keep some original data after mesh processing
- processing in uniformTriangleGroup
- to be used in, e.g., geometry export
2021-05-28 23:43:37 -07:00
Alexander Rose
051b48776e consider BB atoms as trace 2021-05-28 23:37:25 -07:00
Alexander Rose
26054681d8 color smoothing param tweaks 2021-05-28 23:36:59 -07:00
Alexander Rose
70fa85d7d4 fix assembly-symmetry off option
- would not remove cage/axes visuals
2021-05-28 23:04:08 -07:00
Alexander Rose
5a23cd483e Merge pull request #198 from sukolsak/export-auto-quality
Geometry export: auto adjust quality of sphere/cylinder meshes
2021-05-28 22:57:19 -07:00
Alexander Rose
d759b07f1b Merge pull request #193 from JonStargaryen/anvil-fixes
ANVIL fixes
2021-05-28 22:52:49 -07:00
Sukolsak Sakshuwong
4694da0057 auto adjust quality of sphere/cylinder meshes 2021-05-28 11:05:06 -07:00
JonStargaryen
f930e3dbe0 avoiid cast 2021-05-28 08:43:05 -07:00
Alexander Rose
fcf45d20be Merge pull request #197 from sukolsak/gltf-material
Geometry export: add material to GLB
2021-05-27 22:53:45 -07:00
JonStargaryen
ad4ba7bcf9 drop redundant ops 2021-05-27 16:15:06 -07:00
JonStargaryen
26644ede49 no need to reassign neighbors 2021-05-27 15:41:48 -07:00
JonStargaryen
810973ff54 cleanup 2021-05-27 14:07:40 -07:00
JonStargaryen
6ad09c60c0 cleanup 2021-05-27 14:01:40 -07:00
JonStargaryen
dc146f5f04 Eisenhaber 1995 improvements 2021-05-27 12:38:35 -07:00
JonStargaryen
e1b771bba4 use existing impl 2021-05-27 09:48:31 -07:00
Sukolsak Sakshuwong
e2ab3a6fd6 add material to glb 2021-05-27 07:33:28 -07:00
JonStargaryen
d1296de676 bs on ordered array 2021-05-26 14:11:31 -07:00
JonStargaryen
fcac1a62c6 sample only 1 hemisphere 2021-05-26 12:54:03 -07:00
JonStargaryen
5eafddf97a fine-grained updates for large structures 2021-05-26 12:02:04 -07:00
JonStargaryen
e2dcbc3d65 const 2021-05-26 11:26:28 -07:00
JonStargaryen
54a388da9c store hphobhphil stats 2021-05-26 09:58:58 -07:00
JonStargaryen
3849c341b8 no optional chaining 2021-05-26 09:30:06 -07:00
JonStargaryen
31f4803c0a no array copies 2021-05-26 09:21:42 -07:00
JonStargaryen
d6e36d4ca7 cleanup 2021-05-25 14:55:49 -07:00
JonStargaryen
0d526fdc98 drop location in some more places 2021-05-25 14:35:19 -07:00
JonStargaryen
04b36170d8 cleanup 2021-05-25 13:15:36 -07:00
JonStargaryen
db787c9ea4 avoid location in filter loop 2021-05-25 12:38:43 -07:00
JonStargaryen
e1e6f9ca48 wip membership 2021-05-25 11:55:22 -07:00
JonStargaryen
40b5605e10 local Vec3 methods 2021-05-25 08:57:08 -07:00
JonStargaryen
609654b689 dont attach ASA values 2021-05-25 08:39:50 -07:00
dsehnal
45ef00f1d1 Move FileHandle.fromDescriptor to /servers to avoid importing 'fs' in browser builds 2021-05-25 17:18:47 +02:00
dsehnal
88380ff917 fix production and debug flags 2021-05-25 16:22:58 +02:00
dsehnal
bc7bfe9788 fix webpack watch build 2021-05-25 16:09:40 +02:00
dsehnal
469ca6cb41 fix webpack build 2021-05-25 15:51:40 +02:00
David Sehnal
c0be790ff1 Merge pull request #195 from MadCatX/update_deps
Update node-sass and webpack dependencies
2021-05-25 15:24:21 +02:00
David Sehnal
8c1d16353e Merge pull request #196 from molstar/sequence-mapping
Basic sequence mapping support
2021-05-25 15:20:49 +02:00
dsehnal
d76d475015 BestDatabaseSequenceMapping superposition 2021-05-25 15:15:33 +02:00
dsehnal
69024152cb Best Database Sequence Mapping property
- assigned based on atom_site.db_name/_acc/_num/_res CIF fields
- added basic color theme
2021-05-25 13:20:35 +02:00
Michal Malý
4a19aedec8 Update node-sass and webpack dependencies 2021-05-24 21:50:25 +02:00
JonStargaryen
df89351301 remove normal vector 2021-05-24 12:24:23 -07:00
JonStargaryen
9a0c87695f cleanup 2021-05-24 11:45:38 -07:00
JonStargaryen
a393231522 inline x/y/z 2021-05-24 10:00:54 -07:00
Alexander Rose
33de60d365 fix typeof check 2021-05-22 22:49:13 -07:00
JonStargaryen
3cf67f7605 fix coloring for trace-only ASA calc 2021-05-22 19:48:02 -07:00
Alexander Rose
ffdcf798e0 Merge pull request #194 from sukolsak/fix-smaa
Fix HTMLImageElement check for SMAA
2021-05-22 14:19:59 -07:00
Sukolsak Sakshuwong
397e1235e7 fix HTMLImageElement check for SMAA 2021-05-22 02:58:59 -07:00
JonStargaryen
4e77699076 more fine-grained status messages 2021-05-21 21:35:57 -07:00
JonStargaryen
b47d046505 mapping still needed for #132 2021-05-21 15:31:49 -07:00
JonStargaryen
74aa24bfa0 traceOnly: check for BB as well 2021-05-21 09:27:02 -07:00
JonStargaryen
30d5b0ddb1 rename to traceOnly 2021-05-21 09:24:09 -07:00
Alexander Rose
1e35ea15eb Merge pull request #192 from sukolsak/gltf-instancing
Geometry export: use instancing in GLB
2021-05-20 23:47:52 -07:00
Sukolsak Sakshuwong
bc998ab328 don't split triangles in Mesh.uniformTriangleGroup
Calling Mesh.uniformTriangleGroup for WebGL1 is required for picking to work properly. With color smoothing implemented, we don't have to split triangles in Mesh.uniformTriangleGroup anymore. This should help reduce the exported file size in WebGL1. This change is suggested by @arose
2021-05-20 17:03:48 -07:00
JonStargaryen
e5e245f4ee scale radius used for evaluation 2021-05-20 14:26:42 -07:00
JonStargaryen
c6073b894a ASA coloring wrong after removing part of a chain #132 2021-05-20 11:38:04 -07:00
Sukolsak Sakshuwong
9b11794f22 store colors in glb using 8 bits per channel 2021-05-20 09:57:59 -07:00
JonStargaryen
f2b9dceaab ANVIL runtime updates 2021-05-20 09:16:19 -07:00
Sukolsak Sakshuwong
9ccaaf6c80 cleanup 2021-05-20 07:20:56 -07:00
Sukolsak Sakshuwong
ecb97e525e omit indices from texture-mesh in glb 2021-05-20 04:44:13 -07:00
dsehnal
c36c6a6d97 support nested Lookup3D queries
- fixes non-covalent interactions bug
2021-05-20 09:34:59 +02:00
JonStargaryen
60b92471f1 cleanup 2021-05-19 19:34:06 -07:00
JonStargaryen
79e283cfbd wip ANVIL debugging 2021-05-19 15:22:16 -07:00
Sukolsak Sakshuwong
3778dacb08 min and max only required for vertex arrays 2021-05-18 22:27:58 -07:00
Sukolsak Sakshuwong
e407f7279b add generator info to glb 2021-05-18 22:25:37 -07:00
Sukolsak Sakshuwong
ea54209414 gltf instancing 2021-05-18 19:48:59 -07:00
JonStargaryen
d10a36509b ignore non-canonical aa 2021-05-18 15:03:48 -07:00
JonStargaryen
4af560e63a visuals cleanup 2021-05-18 12:23:55 -07:00
JonStargaryen
ecb8900258 wip adjust thickness 2021-05-18 09:49:50 -07:00
Alexander Rose
7bfc1b0ebc sreenshot tweaks
- don't turn of screenspace antialiasing
- use higher numbr of AO samples than default
2021-05-16 12:04:00 -07:00
Alexander Rose
5edae9d6f7 Merge pull request #173 from molstar/smcol
Color smoothing
2021-05-16 11:39:44 -07:00
Alexander Rose
fe702a8c63 fix high resolution molecular surface artefacts
- adjust probePositions prop with quality
2021-05-16 11:33:03 -07:00
Alexander Rose
c8868464a5 color smoothing param tweaks
- reduce sample stride to avoid artefacts
- min color grid resolution for better smoothing at high mesh resolution
2021-05-16 11:31:57 -07:00
Alexander Rose
720e65d2e6 Merge pull request #189 from sukolsak/volume-color
Geometry export: support volume & volumeInstance color types
2021-05-16 10:39:34 -07:00
Sukolsak Sakshuwong
b5123ff36a fix drawCount 2021-05-16 05:33:58 -07:00
Sukolsak Sakshuwong
d237034e8e quantize colors when exporting OBJ 2021-05-16 00:28:31 -07:00
Sukolsak Sakshuwong
aab95d27e0 convert sRGB to linear when exporting GLB 2021-05-15 23:56:50 -07:00
Sukolsak Sakshuwong
c68306125e support volume & volumeInstance color type in geo exporters 2021-05-15 23:55:21 -07:00
Alexander Rose
3173396737 use constant from gl context if available 2021-05-15 11:19:30 -07:00
Alexander Rose
212a3eeb6c tweaked auto color smoothing props 2021-05-15 11:18:39 -07:00
Alexander Rose
17b25354f5 added isPromiseLike helper
- use instead of instanceof Promise
2021-05-15 11:16:32 -07:00
JonStargaryen
9f176bd2bc add alpha-oonly option to asa calc 2021-05-12 12:20:28 -07:00
Alexander Rose
4a78283ce1 changelog 2021-05-09 21:10:43 -07:00
Alexander Rose
81e29533dc Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-05-09 21:09:19 -07:00
Alexander Rose
c1a2c602a1 remove unfinished dColorGridType support 2021-05-09 21:08:19 -07:00
Alexander Rose
c436653ce9 made transparency helpers a bit more generic 2021-05-09 14:35:00 -07:00
Alexander Rose
a2b4ed7c1c queries: add nos-bridge, improve disulfide-bridge 2021-05-09 13:56:40 -07:00
Alexander Rose
83968aa408 cylinder mesh builder improvements 2021-05-09 13:09:00 -07:00
dsehnal
71539cc75a citation in readme 2021-05-09 11:53:16 +02:00
dsehnal
881d4d2a99 Fix IndexPairBonds for structures with re-ordered atoms 2021-05-09 11:46:20 +02:00
David Sehnal
ae2f2e7d0e Merge pull request #181 from molstar/spin-struct-anim
Spin structure animation
2021-05-09 10:47:14 +02:00
Alexander Rose
e31f0f7660 basic spin-structure animation 2021-05-08 23:54:27 -07:00
Alexander Rose
3586207968 better SO autobonding 2021-05-08 19:55:36 -07:00
Alexander Rose
b575793b83 Merge pull request #180 from sukolsak/fix-sheet-caps
Fix normals in sheet caps
2021-05-08 18:06:30 -07:00
Alexander Rose
81bf653790 Merge pull request #179 from sukolsak/fix-arrow
Fix normals in sheet arrows
2021-05-08 18:03:30 -07:00
Sukolsak Sakshuwong
6186c60cd9 fix normals in sheet caps 2021-05-08 00:03:42 -07:00
Sukolsak Sakshuwong
6ab480589a fix lint 2021-05-07 23:55:53 -07:00
Sukolsak Sakshuwong
571f8187c3 fix normals in sheet arrows 2021-05-07 23:29:35 -07:00
Alexander Rose
d510ff00dc Merge pull request #177 from sukolsak/per-group-transparency
Add per-group transparency support to geometry exporters
2021-05-06 23:10:08 -07:00
Sukolsak Sakshuwong
7a0f286fb4 add per-group transparency support to geo-export 2021-05-05 17:54:27 -07:00
Alexander Rose
fccf8d6b87 Merge pull request #174 from sukolsak/glb-export
GLB and STL export
2021-05-04 22:22:41 -07:00
Sukolsak Sakshuwong
e0c08e89d0 use more descriptive labels for geometry exporters 2021-05-04 20:40:28 -07:00
Sukolsak Sakshuwong
ef9885411c small cleanup 2021-05-04 07:45:16 -07:00
Sukolsak Sakshuwong
7542ead360 address @arose's comments 2021-05-03 23:57:32 -07:00
Sukolsak Sakshuwong
043ab08066 fix normal vectors being zero 2021-05-03 18:11:36 -07:00
Sukolsak Sakshuwong
ec0933d197 update changelog 2021-05-03 12:20:20 -07:00
Sukolsak Sakshuwong
aef34a687d glb and stl export 2021-05-03 12:19:28 -07:00
Alexander Rose
9b7192f261 color smoothing for element-index theme 2021-05-03 01:02:34 -07:00
Alexander Rose
18212d9ee7 cpu trilinear interpolation for color smoothing 2021-05-03 00:48:14 -07:00
Alexander Rose
8d4e0730e8 improved color smoothing params handling 2021-05-02 23:34:19 -07:00
Alexander Rose
ba8e9e189f auto-smooth colors if preferred 2021-05-01 18:59:06 -07:00
Alexander Rose
b7935de7af limit color smoothing to group-based themes 2021-05-01 18:57:42 -07:00
Alexander Rose
10ca32f9d7 volumetric color smoothing
- for Mesh and TextureMesh geometries
2021-05-01 18:29:30 -07:00
Alexander Rose
bb86d83c96 better granularity in element-symbol color theme 2021-05-01 17:48:35 -07:00
Alexander Rose
907b08cc99 wip, color grid shader support
- only tested for mesh
2021-05-01 17:47:53 -07:00
Alexander Rose
a07d593909 tweak printImageData debug helper 2021-05-01 17:43:43 -07:00
Alexander Rose
a0a3ff1969 Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-05-01 17:42:52 -07:00
dsehnal
7fac8a8f77 2.0.5 2021-04-26 16:02:31 +02:00
dsehnal
7266c67e32 2.0.5 changelog 2021-04-26 16:00:31 +02:00
dsehnal
50c8d09742 default camera radius set to 0 2021-04-26 15:13:46 +02:00
dsehnal
7377947975 Changelog 2021-04-25 15:15:26 +02:00
David Sehnal
a3c4daf30a Merge pull request #169 from sukolsak/texture-mesh-export
Add TextureMesh export support
2021-04-25 15:13:26 +02:00
Sukolsak Sakshuwong
9d7e6f1d99 add TextureMesh export support 2021-04-25 05:04:03 -07:00
dsehnal
9e105020e3 lazy volume loading 2021-04-25 12:10:09 +02:00
Alexander Rose
698f7e16bd Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-04-24 09:42:34 -07:00
Alexander Rose
93df548cfe add torus primitive and fix render tests 2021-04-23 22:55:20 -07:00
Alexander Rose
a0b1593c82 add MeshBuilder.addMesh 2021-04-23 22:12:19 -07:00
Alexander Rose
fc81e08d73 Support full pausing (no draw) rendering 2021-04-23 22:10:01 -07:00
Alexander Rose
5369fa5adf canvas viewport support fixes and improvements
- restrict ssao to viewport bounds
- only downscale ssao buffer (not upscale)
- avoid zero camera radius/radiusMax to allow camera movements in empty scenes and to avoid ssao artifacts
2021-04-23 22:07:34 -07:00
dsehnal
316a77c716 guard against non-invertible matrices in Camera.update
+ relative viewports and dynamic updating of them sometimes caused non-invertible matrix
2021-04-23 19:05:29 +02:00
dsehnal
42dfa69ad7 Residue list selection helper 2021-04-21 21:24:28 +02:00
dsehnal
cae4eb8b0e await screenshot clipboard write & fallback to <img> on fail 2021-04-21 20:08:37 +02:00
David Sehnal
5514b24fdf Merge pull request #167 from molstar/multi-canvas3d
Multi-canvas support for PluginContext
2021-04-21 20:05:05 +02:00
dsehnal
d570bc352e "relative" canvas3d viewport and picking dimensions fix 2021-04-20 20:26:13 +02:00
dsehnal
8a76a3fa64 2.0.4 2021-04-20 11:23:23 +02:00
dsehnal
71bf4e21f5 changelog 2021-04-20 11:21:29 +02:00
dsehnal
e0d36c30d3 Fix measurement labels & interactions 2021-04-20 11:19:15 +02:00
Alexander Rose
d653a96b25 Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-04-18 13:12:12 -07:00
David Sehnal
b53debcfef Merge pull request #163 from sukolsak/fix-zip-progress
Fix incorrect deflate progress
2021-04-14 15:02:15 +02:00
Sukolsak Sakshuwong
d0705ac226 fix deflate progress 2021-04-14 05:07:05 -07:00
Alexander Rose
e01eacb3fe changelog 2021-04-13 23:12:39 -07:00
Alexander Rose
d4102b476b Fix, read SDF multi-line values 2021-04-13 23:07:55 -07:00
dsehnal
83ce17174a changelog 2021-04-13 21:17:28 +02:00
dsehnal
18023d7f26 Merge branch 'master' of https://github.com/molstar/molstar 2021-04-13 21:16:28 +02:00
dsehnal
a8541d5967 Structure.eachAtomicHierarchyElement 2021-04-13 21:15:44 +02:00
David Sehnal
8b21818f2e Merge pull request #159 from sukolsak/obj-export
OBJ Export
2021-04-13 17:06:48 +02:00
Sukolsak Sakshuwong
0b290247dc show partial progress when exporting large meshes 2021-04-11 13:15:50 -07:00
Sukolsak Sakshuwong
fb5010e962 reorder arguments to addMeshWithColors() 2021-04-10 18:12:32 -07:00
Sukolsak Sakshuwong
178789d327 make RenderObjectExporter.add() async 2021-04-10 18:10:42 -07:00
Sukolsak Sakshuwong
4fae526073 wip 2021-04-09 15:59:14 -07:00
Alexander Rose
e0a594121b wip, color smoothing experiments 2021-04-06 23:36:29 -07:00
614 changed files with 33536 additions and 29193 deletions

View File

@@ -38,7 +38,24 @@
"selector": "ExportDefaultDeclaration",
"message": "Default exports are not allowed"
}
]
],
"no-throw-literal": "error",
"key-spacing": "error",
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": "error",
"space-in-parens": "error",
"computed-property-spacing": "error",
"prefer-const": ["error", {
"destructuring": "all",
"ignoreReadBeforeAssign": false
}],
"space-before-function-paren": "off",
"func-call-spacing": "off",
"no-multi-spaces": "error",
"block-spacing": "error",
"keyword-spacing": "off",
"space-before-blocks": "error",
"semi-spacing": "error"
},
"overrides": [
{
@@ -89,7 +106,14 @@
"error",
"1tbs", { "allowSingleLine": true }
],
"@typescript-eslint/comma-spacing": "error"
"@typescript-eslint/comma-spacing": "error",
"@typescript-eslint/space-before-function-paren": ["error", {
"anonymous": "always",
"named": "never",
"asyncArrow": "always"
}],
"@typescript-eslint/func-call-spacing": ["error"],
"@typescript-eslint/keyword-spacing": ["error"]
}
}
]

View File

@@ -1,18 +0,0 @@
on:
push:
pull_request:
jobs:
eslint:
name: eslint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: install node v12
uses: actions/setup-node@v1
with:
node-version: 12
- name: yarn install
run: yarn install
- name: eslint
uses: icrawl/action-eslint@v1

20
.github/workflows/node.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
- run: npm ci
- run: sudo apt-get install xvfb
- name: Lint
run: npm run lint
- name: Test
run: xvfb-run --auto-servernum npm run jest
- name: Build
run: npm run build

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ tsconfig.commonjs.tsbuildinfo
*.sublime-workspace
.idea
.DS_Store

View File

@@ -3,34 +3,210 @@ All notable changes to this project will be documented in this file, following t
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
## [v2.0.3] - 2021-04-09
### Added
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
## [v2.4.0] - 2021-11-25
### Changed
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
- Fix secondary-structure property handling
- StructureElement.Property was incorrectly resolving type & key
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
- Re-enable VAO with better workaround (bind null elements buffer before deleting)
- Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes)
- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces
## [v2.3.9] - 2021-11-20
- Workaround: switch off VAO support for now
## [v2.3.8] - 2021-11-20
- Fix double canvas context creation (in plugin context)
- Fix unused vertex attribute handling (track which are used, disable the rest)
- Workaround for VAO issue in Chrome 96 (can cause WebGL to crash on geometry updates)
## [v2.3.7] - 2021-11-15
- Added ``ViewerOptions.collapseRightPanel``
- Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
- Fix: handle parent in Structure.remapModel
- Add ``rounded`` and ``square`` helix profile options to Cartoon representation (in addition to the default ``elliptical``)
## [v2.3.6] - 2021-11-8
- Add additional measurement controls: orientation (box, axes, ellipsoid) & plane (best fit)
- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
- Fix marker highlight color overriding select color
- CellPack extension update
- add binary model support
- add compartment (including membrane) geometry support
- add latest mycoplasma model example
- Prefer WebGL1 in Safari 15.1.
## [v2.3.5] - 2021-10-19
- Fix sequence viewer for PDB files with COMPND record and multichain entities.
- Fix index pair bonds order assignment
## [v2.3.4] - 2021-10-12
- Fix pickScale not taken into account in line/point shader
- Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
- Fix selecting bonds not adding their atoms in selection manager
- Add ``preferAtoms`` option to SelectLoci/HighlightLoci behaviors
- Make the implicit atoms of bond visuals pickable
- Add ``preferAtomPixelPadding`` to Canvas3dInteractionHelper
- Add points & crosses visuals to Line representation
- Add ``pickPadding`` config option (look around in case target pixel is empty)
- Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
- Fix ``argparse`` config in servers.
## [v2.3.3] - 2021-10-01
- Fix direct volume shader
## [v2.3.2] - 2021-10-01
- Prefer WebGL1 on iOS devices until WebGL2 support has stabilized.
## [v2.3.1] - 2021-09-28
- Add Charmm saccharide names
- Treat missing occupancy column as occupancy of 1
- Fix line shader not accounting for aspect ratio
- [Breaking] Fix point repr & shader
- Was unusable with ``wboit``
- Replaced ``pointFilledCircle`` & ``pointEdgeBleach`` params by ``pointStyle`` (square, circle, fuzzy)
- Set ``pointSizeAttenuation`` to false by default
- Set ``sizeTheme`` to ``uniform`` by default
- Add ``markerPriority`` option to Renderer (useful in combination with edges of marking pass)
- Add support support for ``chem_comp_bond`` and ``struct_conn`` categories (fixes ModelServer behavior where these categories should have been present)
- Model and VolumeServer: fix argparse config
## [v2.3.0] - 2021-09-06
- Take include/exclude flags into account when displaying aromatic bonds
- Improve marking performance
- Avoid unnecessary draw calls/ui updates when marking
- Check if loci is superset of visual
- Check if loci overlaps with unit visual
- Ensure ``Interval`` is used for ranges instead of ``SortedArray``
- Add uniform marker type
- Special case for reversing previous mark
- Add optional marking pass
- Outlines visible and hidden parts of highlighted/selected groups
- Add highlightStrength/selectStrength renderer params
## [v2.2.3] - 2021-08-25
- Add ``invertCantorPairing`` helper function
- Add ``Mesh`` processing helper ``.smoothEdges``
- Smooth border of molecular-surface with ``includeParent`` enabled
- Hide ``includeParent`` option from gaussian-surface visuals (not particularly useful)
- Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models)
- Fix new ``TransformData`` issues (camera/bounding helper not showing up)
- Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``)
## [v2.2.2] - 2021-08-11
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
- Fix ``mol-script`` query compiler const expression recognition.
## [v2.2.1] - 2021-08-02
- Add surrounding atoms (5 Angstrom) structure selection query
- [Breaking] Add maxDistance prop to ``IndexPairBonds``
- Fix coordinateSystem not handled in ``Structure.asParent``
- Add ``dynamicBonds`` to ``Structure`` props (force re-calc on model change)
- Expose as optional param in root structure transform helper
- Add overpaint support to geometry exporters
- ``InputObserver`` improvements
- normalize wheel speed across browsers/platforms
- support Safari gestures (used by ``TrackballControls``)
- ``PinchInput.fractionDelta`` and use it in ``TrackballControls``
## [v2.2.0] - 2021-07-31
- Add ``tubularHelices`` parameter to Cartoon representation
- Add ``SdfFormat`` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
- Fix mononucleotides detected as polymer components (#229)
- Set default outline scale back to 1
- Improved DCD reader cell angle handling (interpret near 0 angles as 90 deg)
- Handle more residue/atom names commonly used in force-fields
- Add USDZ support to ``geo-export`` extension.
- Fix ``includeParent`` support for multi-instance bond visuals.
- Add ``operator`` Loci granularity, selecting everything with the same operator name.
- Prefer ``_label_seq_id`` fields in secondary structure assignment.
- Support new EMDB API (https://www.ebi.ac.uk/emdb/api/entry/map/[EMBD-ID]) for EM volume contour levels.
- ``Canvas3D`` tweaks:
- Update ``forceDraw`` logic.
- Ensure the scene is re-rendered when viewport size changes.
- Support ``noDraw`` mode in ``PluginAnimationLoop``.
## [v2.1.0] - 2021-07-05
- Add parameter for to display aromatic bonds as dashes next to solid cylinder/line.
- Add backbone representation
- Fix outline in orthographic mode and set default scale to 2.
## [v2.0.7] - 2021-06-23
- Add ability to specify ``volumeIndex`` in ``Viewer.loadVolumeFromUrl`` to better support Volume Server inputs.
- Support in-place reordering for trajectory ``Frame.x/y/z`` arrays for better memory efficiency.
- Fixed text CIF encoder edge cases (most notably single whitespace not being escaped).
## [v2.0.6] - 2021-06-01
- Add glTF (GLB) and STL support to ``geo-export`` extension.
- Protein crosslink improvements
- Change O-S bond distance to allow for NOS bridges (doi:10.1038/s41586-021-03513-3)
- Added NOS-bridges query & improved disulfide-bridges query
- Fix #178: ``IndexPairBonds`` for non-single residue structures (bug due to atom reordering).
- Add volumetric color smoothing for MolecularSurface and GaussianSurface representations (#173)
- Fix nested 3d grid lookup that caused results being overwritten in non-covalent interactions computation.
- Basic implementation of ``BestDatabaseSequenceMapping`` (parse from CIF, color theme, superposition).
- Add atom id ranges support to Selection UI.
## [v2.0.5] - 2021-04-26
- Ability to pass ``Canvas3DContext`` to ``PluginContext.fromCanvas``.
- Relative frame support for ``Canvas3D`` viewport.
- Fix bug in screenshot copy UI.
- Add ability to select residues from a list of identifiers to the Selection UI.
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
- Add ``MeshBuilder.addMesh``.
- Add ``Torus`` primitive.
- Lazy volume loading support.
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
- Add ``TextureMesh`` support to ``geo-export`` extension.
## [v2.0.4] - 2021-04-20
- [WIP] Mesh export extension
- ``Structure.eachAtomicHierarchyElement`` (#161)
- Fixed reading multi-line values in SDF format
- Fixed Measurements UI labels (#166)
## [v2.0.3] - 2021-04-09
- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- `Canvas3D.getRenderObjects`.
- Add ``Canvas3D.getRenderObjects``.
- [WIP] Animate state interpolating, including model trajectories
### Changed
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
### Fixed
- VolumeFromDensityServerCif transform label
- Fix VolumeFromDensityServerCif transform label
## [v2.0.1] - 2021-03-23
### Fixed
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
## [v2.0.0] - 2021-03-23
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

View File

@@ -7,6 +7,9 @@
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
When using Mol*, please cite:
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: [Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures](https://doi.org/10.1093/nar/gkab314), *Nucleic Acids Research*, 2021; https://doi.org/10.1093/nar/gkab314.
## Project Structure Overview
@@ -119,9 +122,9 @@ and navigate to `build/viewer`
**Convert any CIF to BinaryCIF**
node lib/servers/model/preprocess -i file.cif -ob file.bcif
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
To see all available commands, use ``node lib/servers/model/preprocess -h``.
To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``.
Or

View File

@@ -2,11 +2,11 @@ audit.block_doi
database_code.depnum_ccdc_archive
database_code.depnum_ccdc_fiz
database_code.ICSD
database_code.MDF
database_code.NBS
database_code.CSD
database_code.COD
database_code.icsd
database_code.mdf
database_code.nbs
database_code.csd
database_code.cod
chemical.name_systematic
chemical.name_common
@@ -24,8 +24,8 @@ atom_type_scat.dispersion_imag
atom_type_scat.source
space_group.crystal_system
space_group.name_H-M_full
space_group.IT_number
space_group.name_h-m_full
space_group.it_number
space_group_symop.operation_xyz
cell.length_a
@@ -35,14 +35,14 @@ cell.angle_alpha
cell.angle_beta
cell.angle_gamma
cell.volume
cell.formula_units_Z
cell.formula_units_z
atom_site.label
atom_site.type_symbol
atom_site.fract_x
atom_site.fract_y
atom_site.fract_z
atom_site.U_iso_or_equiv
atom_site.u_iso_or_equiv
atom_site.adp_type
atom_site.occupancy
atom_site.calc_flag
@@ -52,20 +52,13 @@ atom_site.disorder_group
atom_site.site_symmetry_multiplicity
atom_site_aniso.label
atom_site_aniso.U
atom_site_aniso.U_11
atom_site_aniso.U_22
atom_site_aniso.U_33
atom_site_aniso.U_23
atom_site_aniso.U_13
atom_site_aniso.U_12
atom_site_aniso.U_su
atom_site_aniso.U_11_su
atom_site_aniso.U_22_su
atom_site_aniso.U_33_su
atom_site_aniso.U_23_su
atom_site_aniso.U_13_su
atom_site_aniso.U_12_su
atom_site_aniso.u
atom_site_aniso.u_11
atom_site_aniso.u_22
atom_site_aniso.u_33
atom_site_aniso.u_23
atom_site_aniso.u_13
atom_site_aniso.u_12
geom_bond.atom_site_label_1
geom_bond.atom_site_label_2
1 audit.block_doi
2 database_code.depnum_ccdc_archive
3 database_code.depnum_ccdc_fiz
4 database_code.ICSD database_code.icsd
5 database_code.MDF database_code.mdf
6 database_code.NBS database_code.nbs
7 database_code.CSD database_code.csd
8 database_code.COD database_code.cod
9 chemical.name_systematic
10 chemical.name_common
11 chemical.melting_point
12 chemical_formula.moiety
24 cell.length_a
25 cell.length_b
26 cell.length_c
27 cell.angle_alpha
28 cell.angle_beta
29 cell.angle_gamma
30 cell.volume
31 cell.formula_units_Z cell.formula_units_z
35 atom_site.fract_y
36 atom_site.fract_z
37 atom_site.U_iso_or_equiv atom_site.u_iso_or_equiv
38 atom_site.adp_type
39 atom_site.occupancy
40 atom_site.calc_flag
41 atom_site.refinement_flags
42 atom_site.disorder_assembly
43 atom_site.disorder_group
44 atom_site.site_symmetry_multiplicity
45 atom_site_aniso.label
46 atom_site_aniso.U atom_site_aniso.u
47 atom_site_aniso.U_11 atom_site_aniso.u_11
48 atom_site_aniso.U_22 atom_site_aniso.u_22
52 atom_site_aniso.U_12 atom_site_aniso.u_12
53 atom_site_aniso.U_su geom_bond.atom_site_label_1
54 atom_site_aniso.U_11_su geom_bond.atom_site_label_2
55 atom_site_aniso.U_22_su geom_bond.distance
56 atom_site_aniso.U_33_su geom_bond.site_symmetry_1
57 atom_site_aniso.U_23_su geom_bond.site_symmetry_2
58 atom_site_aniso.U_13_su geom_bond.publ_flag
59 atom_site_aniso.U_12_su geom_bond.valence
60
61
geom_bond.distance
geom_bond.site_symmetry_1
geom_bond.site_symmetry_2
geom_bond.publ_flag
geom_bond.valence
62
63
64

View File

@@ -28,3 +28,8 @@
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
* Long linear sugar chain (4HG6)
* Anisotropic B-factors/Ellipsoids (1EJG)
* NOS bridges (LYS-CSO in 7B0L, 6ZWJ, 6ZWH)
Assembly symmetries
* 5M30 (Assembly 1, C3 local and pseudo)
* 1RB8 (Assembly 1, I global)

22536
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.0.3",
"version": "2.4.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -27,9 +27,9 @@
"watch-tsc": "tsc --watch --incremental",
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
"watch-extra": "cpx \"src/**/*.{scss,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",
"watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
"watch-webpack": "webpack -w --mode development --stats minimal",
"watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js",
"watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js",
"serve": "http-server -p 1338 -g",
"model-server": "node lib/commonjs/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
@@ -88,65 +88,71 @@
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^2.0.2",
"@graphql-codegen/cli": "^1.19.4",
"@graphql-codegen/time": "^2.0.2",
"@graphql-codegen/typescript": "^1.19.0",
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
"@graphql-codegen/typescript-operations": "^1.17.12",
"@types/cors": "^2.8.8",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"@graphql-codegen/add": "^3.1.0",
"@graphql-codegen/cli": "^2.3.0",
"@graphql-codegen/time": "^3.1.0",
"@graphql-codegen/typescript": "^2.4.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
"@graphql-codegen/typescript-operations": "^2.2.1",
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"benchmark": "^2.1.4",
"concurrently": "^5.3.0",
"cpx2": "^3.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.5.1",
"eslint": "^8.3.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^9.0.1",
"graphql": "^15.4.0",
"http-server": "^0.12.3",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.3.2",
"node-sass": "^5.0.0",
"fs-extra": "^10.0.0",
"graphql": "^15.7.2",
"http-server": "^14.0.0",
"jest": "^27.3.1",
"mini-css-extract-plugin": "^2.4.5",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass-loader": "^10.1.0",
"simple-git": "^2.25.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.2.3",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-version-file-plugin": "^0.4.0"
"sass": "^1.43.5",
"sass-loader": "^12.3.0",
"simple-git": "^2.47.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.5.2",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^2.1.0",
"@types/compression": "1.7.0",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.18",
"@types/node": "^14.14.11",
"@types/node-fetch": "^2.5.7",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/swagger-ui-dist": "3.30.0",
"argparse": "^1.0.10",
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.1",
"@types/compression": "1.7.2",
"@types/express": "^4.17.13",
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^8.0.1",
"immer": "^9.0.7",
"immutable": "^3.8.2",
"node-fetch": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rxjs": "^6.6.6",
"swagger-ui-dist": "^3.37.2",
"tslib": "^2.1.0",
"util.promisify": "^1.0.1",
"xhr2": "^0.2.0"
"node-fetch": "^2.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rxjs": "^7.4.0",
"swagger-ui-dist": "^4.1.1",
"tslib": "^2.3.1",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"
},
"optionalDependencies": {
"gl": "^4.9.2"
}
}

View File

@@ -238,7 +238,7 @@ export class ViewportComponent extends PluginUIComponent {
pocketPreset = () => this.set(PocketPreset);
interactionsPreset = () => this.set(InteractionsPreset);
get showButtons () {
get showButtons() {
return this.plugin.config.get(ShowButtons);
}

View File

@@ -21,7 +21,7 @@
<script type="text/javascript" src="./molstar.js"></script>
<script type="text/javascript">
var viewer = new molstar.Viewer('app', {
layoutIsExpanded: false,
layoutIsExpanded: true,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
@@ -36,7 +36,8 @@
emdbProvider: 'rcsb',
});
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
</script>
</body>

View File

@@ -52,12 +52,22 @@
var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
var mapProvider = getParam('map-provider', '[^&]+').trim().toLowerCase();
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
var pickScale = getParam('pick-scale', '[^&]+').trim();
var pickPadding = getParam('pick-padding', '[^&]+').trim();
var viewer = new molstar.Viewer('app', {
layoutShowControls: !hideControls,
viewportShowExpand: false,
collapseLeftPanel: collapseLeftPanel,
pdbProvider: pdbProvider || 'pdbe',
emdbProvider: emdbProvider || 'pdbe',
volumeStreamingServer: (mapProvider || 'pdbe') === 'rcsb'
? 'https://maps.rcsb.org'
: 'https://www.ebi.ac.uk/pdbe/densities',
pixelScale: parseFloat(pixelScale) || 1,
pickScale: parseFloat(pickScale) || 0.25,
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
});
var snapshotId = getParam('snapshot-id', '[^&]+').trim();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -9,24 +9,28 @@ import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { GeometryExport } from '../../extensions/geo-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectSelector } from '../../mol-state';
@@ -55,7 +59,8 @@ const Extensions = {
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport)
};
const DefaultViewerOptions = {
@@ -69,9 +74,12 @@ const DefaultViewerOptions = {
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
disableAntialiasing: false,
pixelScale: 1,
enableWboit: true,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -110,7 +118,7 @@ export class Viewer {
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
@@ -128,6 +136,8 @@ export class Viewer {
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
@@ -241,36 +251,139 @@ export class Viewer {
}));
}
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
/**
* @example Load X-ray density from volume server
viewer.loadVolumeFromUrl({
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1.5,
color: 0x3362B2
}, {
type: 'relative',
value: 3,
color: 0x33BB33,
volumeIndex: 1
}, {
type: 'relative',
value: -3,
color: 0xBB3333,
volumeIndex: 1
}], {
entryId: ['2FO-FC', 'FO-FC'],
isLazy: true
});
* *********************
* @example Load EM density from volume server
viewer.loadVolumeFromUrl({
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1,
color: 0x3377aa
}], {
entryId: 'EMD-30210',
isLazy: true
});
*/
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
});
return update.commit();
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build().to(volume);
const repr = plugin.build();
for (const iso of isovalues) {
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
repr
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
});
}
/**
* @example
* viewer.loadTrajectory({
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
* preset: 'all-models' // or 'default'
* });
*/
async loadTrajectory(params: LoadTrajectoryParams) {
const plugin = this.plugin;
let model: StateObjectSelector, coords: StateObjectSelector;
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
const data = params.model.kind === 'model-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
model = await plugin.builders.structure.createModel(trajectory);
} else {
const data = params.model.kind === 'topology-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const provider = plugin.dataFormats.get(params.model.format);
model = await provider!.parse(plugin, data);
}
{
const data = params.coordinates.kind === 'coordinates-data'
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
const provider = plugin.dataFormats.get(params.coordinates.format);
coords = await provider!.parse(plugin, data);
}
const trajectory = await plugin.build().toRoot()
.apply(TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coords.ref
}, { dependsOn: [model.ref, coords.ref] })
.commit();
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
return { model, coords, preset };
}
handleResize() {
this.plugin.layout.events.updated.next();
this.plugin.layout.events.updated.next(void 0);
}
}
@@ -282,5 +395,18 @@ export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number
alpha?: number,
volumeIndex?: number
}
export interface LoadTrajectoryParams {
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
modelLabel?: string,
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
coordinatesLabel?: string,
preset?: keyof PresetTrajectoryHierarchy
}

View File

@@ -19,7 +19,7 @@ import { ensureDataAvailable, readCCD } from './util';
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
const ionNames: string[] = [];
for (const k in ccd) {
const {chem_comp} = ccd[k];
const { chem_comp } = ccd[k];
if (chem_comp.name.value(0).toUpperCase().includes(' ION')) {
ionNames.push(chem_comp.id.value(0));
}
@@ -54,20 +54,20 @@ async function run(out: string, forceDownload = false) {
}
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Extract and save IonNames from CCD.'
});
parser.addArgument('out', {
parser.add_argument('out', {
help: 'Generated file output path.'
});
parser.addArgument([ '--forceDownload', '-f' ], {
action: 'storeTrue',
parser.add_argument('--forceDownload', '-f', {
action: 'store_true',
help: 'Force download of CCD and PVCD.'
});
interface Args {
out: string,
forceDownload?: boolean,
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
run(args.out, args.forceDownload);

View File

@@ -171,7 +171,7 @@ async function createBonds(
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
});
const bondDatabase = Database.ofTables(
const bondDatabase = Database.ofTables(
CCB_TABLE_NAME,
{ chem_comp_bond: mmCIF_chemCompBond_schema },
{ chem_comp_bond: bondTable }
@@ -265,21 +265,21 @@ const CCB_TABLE_NAME = 'CHEM_COMP_BONDS';
const CCA_TABLE_NAME = 'CHEM_COMP_ATOMS';
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Create a cif file with one big table of all chem_comp_bond entries from the CCD and PVCD.'
});
parser.addArgument('out', {
parser.add_argument('out', {
help: 'Generated file output path.'
});
parser.addArgument([ '--forceDownload', '-f' ], {
action: 'storeTrue',
parser.add_argument('--forceDownload', '-f', {
action: 'store_true',
help: 'Force download of CCD and PVCD.'
});
parser.addArgument([ '--binary', '-b' ], {
action: 'storeTrue',
parser.add_argument('--binary', '-b', {
action: 'store_true',
help: 'Output as BinaryCIF.'
});
parser.addArgument(['--ccaOut', '-a'], {
parser.add_argument('--ccaOut', '-a', {
help: 'Optional generated file output path for chem_comp_atom data.',
required: false
});
@@ -289,6 +289,6 @@ interface Args {
binary?: boolean,
ccaOut?: string
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
run(args.out, args.binary, args.forceDownload, args.ccaOut);

View File

@@ -37,20 +37,20 @@ function run(args: Args) {
}
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Convert any CIF file to a BCIF file'
});
parser.addArgument([ 'src' ], {
parser.add_argument('src', {
help: 'Source CIF path'
});
parser.addArgument([ 'out' ], {
parser.add_argument('out', {
help: 'Output BCIF path'
});
parser.addArgument([ '-c', '--config' ], {
parser.add_argument('-c', '--config', {
help: 'Optional encoding strategy/precision config path',
required: false
});
parser.addArgument([ '-f', '--filter' ], {
parser.add_argument('-f', '--filter', {
help: 'Optional filter whitelist/blacklist path',
required: false
});
@@ -61,7 +61,7 @@ interface Args {
config?: string
filter?: string
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
if (args) {
run(args);

View File

@@ -124,15 +124,15 @@ async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
const csvFile = parsed.result;
const fieldNamesCol = csvFile.table.getColumn('0');
if (!fieldNamesCol) throw 'error getting fields columns';
if (!fieldNamesCol) throw new Error('error getting fields columns');
const fieldNames = fieldNamesCol.toStringArray();
const filter: Filter = {};
fieldNames.forEach((name, i) => {
const [ category, field ] = name.split('.');
const [category, field] = name.split('.');
// console.log(category, field)
if (!filter[ category ]) filter[ category ] = {};
filter[ category ][ field ] = true;
if (!filter[category]) filter[category] = {};
filter[category][field] = true;
});
return filter;
}
@@ -178,44 +178,44 @@ const CIF_CORE_ATTR_PATH = `${DIC_DIR}/templ_attr.cif`;
const CIF_CORE_ATTR_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/templ_attr.cif';
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Create schema from mmcif dictionary (v50 plus IHM and entity_branch extensions, downloaded from wwPDB)'
});
parser.addArgument([ '--preset', '-p' ], {
defaultValue: '',
parser.add_argument('--preset', '-p', {
default: '',
choices: ['', 'mmCIF', 'CCD', 'BIRD', 'CifCore'],
help: 'Preset name'
});
parser.addArgument([ '--name', '-n' ], {
defaultValue: '',
parser.add_argument('--name', '-n', {
default: '',
help: 'Schema name'
});
parser.addArgument([ '--out', '-o' ], {
parser.add_argument('--out', '-o', {
help: 'Generated schema output path, if not given printed to stdout'
});
parser.addArgument([ '--targetFormat', '-tf' ], {
defaultValue: 'typescript-molstar',
parser.add_argument('--targetFormat', '-tf', {
default: 'typescript-molstar',
choices: ['typescript-molstar', 'json-internal'],
help: 'Target format'
});
parser.addArgument([ '--dicPath', '-d' ], {
defaultValue: '',
parser.add_argument('--dicPath', '-d', {
default: '',
help: 'Path to dictionary'
});
parser.addArgument([ '--fieldNamesPath', '-fn' ], {
defaultValue: '',
parser.add_argument('--fieldNamesPath', '-fn', {
default: '',
help: 'Field names to include'
});
parser.addArgument([ '--forceDicDownload', '-f' ], {
action: 'storeTrue',
parser.add_argument('--forceDicDownload', '-f', {
action: 'store_true',
help: 'Force download of dictionaries'
});
parser.addArgument([ '--moldataImportPath', '-mip' ], {
defaultValue: 'molstar/lib/mol-data',
parser.add_argument('--moldataImportPath', '-mip', {
default: 'molstar/lib/mol-data',
help: 'mol-data import path (for typescript target only)'
});
parser.addArgument([ '--addAliases', '-aa' ], {
action: 'storeTrue',
parser.add_argument('--addAliases', '-aa', {
action: 'store_true',
help: 'Add field name/path aliases'
});
interface Args {
@@ -230,7 +230,7 @@ interface Args {
moldataImportPath: string
addAliases: boolean
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
const FORCE_DIC_DOWNLOAD = args.forceDicDownload;

View File

@@ -34,6 +34,8 @@ export function getFieldType(type: string, description: string, values?: string[
case 'seq-one-letter-code':
case 'author':
case 'orcid_id':
case 'pdbx_PDB_obsoleted_db_id':
case 'pdbx_related_db_id':
case 'sequence_dep':
case 'pdb_id':
case 'emd_id':
@@ -79,9 +81,10 @@ export function getFieldType(type: string, description: string, values?: string[
case 'List(Real,Real)':
case 'List(Real,Real,Real,Real)':
case 'Date':
case 'Datetime':
case 'DateTime':
case 'Tag':
case 'Implied':
case 'Word':
return wrapContainer('str', ',', description, container);
case 'Real':
return wrapContainer('float', ',', description, container);
@@ -187,7 +190,7 @@ function getContainer(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
function getCode(d: Data.CifFrame, imports: Imports, ctx: FrameData): [string, string[] | undefined, string | undefined ] | undefined {
const code = getField('item_type', 'code', d, imports, ctx) || getField('type', 'contents', d, imports, ctx);
if (code) {
return [ code.str(0), getEnums(d, imports, ctx), getContainer(d, imports, ctx) ];
return [code.str(0), getEnums(d, imports, ctx), getContainer(d, imports, ctx)];
} else {
console.log(`item_type.code or type.contents not found for '${d.header}'`);
}
@@ -232,29 +235,26 @@ const FORCE_INT_FIELDS = [
'_struct_sheet_range.end_auth_seq_id',
];
/**
* Note that name and mapped name must share a prefix. This is not always the case in
* the cifCore dictionary, but for downstream code to work a container field with the
* same prefix as the member fields must be given here and in the field names filter
* list.
*/
const FORCE_MATRIX_FIELDS_MAP: { [k: string]: string } = {
'atom_site_aniso.U_11': 'U',
'atom_site_aniso.U_22': 'U',
'atom_site_aniso.U_33': 'U',
'atom_site_aniso.U_23': 'U',
'atom_site_aniso.U_13': 'U',
'atom_site_aniso.U_12': 'U',
'atom_site_aniso.U_11_su': 'U_su',
'atom_site_aniso.U_22_su': 'U_su',
'atom_site_aniso.U_33_su': 'U_su',
'atom_site_aniso.U_23_su': 'U_su',
'atom_site_aniso.U_13_su': 'U_su',
'atom_site_aniso.U_12_su': 'U_su',
'atom_site_aniso.u_11': 'u', // is matrix_u in the the dic
'atom_site_aniso.u_22': 'u',
'atom_site_aniso.u_33': 'u',
'atom_site_aniso.u_23': 'u',
'atom_site_aniso.u_13': 'u',
'atom_site_aniso.u_12': 'u',
};
const FORCE_MATRIX_FIELDS = Object.keys(FORCE_MATRIX_FIELDS_MAP);
const EXTRA_ALIASES: Database['aliases'] = {
'atom_site_aniso.U': [
'atom_site_anisotrop_U'
],
'atom_site_aniso.U_su': [
'atom_site_aniso_U_esd',
'atom_site_anisotrop_U_esd',
'atom_site_aniso.matrix_u': [
'atom_site_anisotrop_U',
'atom_site_aniso.U'
],
};
@@ -317,7 +317,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map())
frames.forEach(d => {
// category definitions in mmCIF start with '_' and don't include a '.'
// category definitions in cifCore don't include a '.'
if (d.header[0] === '_' || d.header.includes('.')) return;
if (d.header[0] === '_' || d.header.includes('.')) return;
const categoryName = d.header.toLowerCase();
// console.log(d.header, d.categoryNames, d.categories)
let descriptionField: Data.CifField | undefined;
@@ -372,7 +372,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map())
const parent_name = item_linked.getField('parent_name');
if (child_name && parent_name) {
for (let i = 0; i < item_linked.rowCount; ++i) {
const childName = child_name.str(i);
const childName: string = child_name.str(i);
const parentName = parent_name.str(i);
if (childName in links && links[childName] !== parentName) {
console.log(`${childName} linked to ${links[childName]}, ignoring link to ${parentName}`);

View File

@@ -8,7 +8,7 @@ import { Database, Filter, Column } from './schema';
import { indentString } from '../../../mol-util/string';
import { FieldPath } from '../../../mol-io/reader/cif/schema';
function header (name: string, info: string, moldataImportPath: string) {
function header(name: string, info: string, moldataImportPath: string) {
return `/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
@@ -22,7 +22,7 @@ import { Database, Column } from '${moldataImportPath}/db';
import Schema = Column.Schema;`;
}
function footer (name: string) {
function footer(name: string) {
return `
export type ${name}_Schema = typeof ${name}_Schema;
export interface ${name}_Database extends Database<${name}_Schema> {};`;
@@ -89,7 +89,7 @@ function doc(description: string, spacesCount: number) {
].join('\n');
}
export function generate (name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
export function generate(name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
const codeLines: string[] = [];
if (fields) {
@@ -128,7 +128,7 @@ export function generate (name: string, info: string, schema: Database, fields:
codeLines.push('');
codeLines.push(`export const ${name}_Aliases = {`);
Object.keys(schema.aliases).forEach(path => {
const [ table, columnName ] = path.split('.');
const [table, columnName] = path.split('.');
if (fields && !fields[table]) return;
if (fields && !fields[table][columnName]) return;

View File

@@ -10,8 +10,8 @@ export function parseImportGet(s: string): Import[] {
// [{'save':hi_ang_Fox_coeffs 'file':templ_attr.cif} {'save':hi_ang_Fox_c0 'file':templ_enum.cif}]
// [{"file":'templ_enum.cif' "save":'H_M_ref'}]
return s.trim().substring(2, s.length - 2).split(/}[ \n\t]*{/g).map(s => {
const save = s.match(/('save'|"save"):([^ \t\n]+)/);
const file = s.match(/('file'|"file"):([^ \t\n]+)/);
const save = s.match(/('save'|"save"):([^ \t\n{}]+)/);
const file = s.match(/('file'|"file"):([^ \t\n{}]+)/);
return {
save: save ? save[0].substr(7).replace(/['"]/g, '') : undefined,
file: file ? file[0].substr(7).replace(/['"]/g, '') : undefined

View File

@@ -51,13 +51,13 @@ export function ListCol(subType: 'int' | 'str' | 'float' | 'coord', separator: s
export type Filter = { [ table: string ]: { [ column: string ]: true } }
export function mergeFilters (...filters: Filter[]) {
export function mergeFilters(...filters: Filter[]) {
const n = filters.length;
const mergedFilter: Filter = {};
const fields: Map<string, number> = new Map();
filters.forEach(filter => {
Object.keys(filter).forEach(category => {
Object.keys(filter[ category ]).forEach(field => {
Object.keys(filter[category]).forEach(field => {
const key = `${category}.${field}`;
const value = fields.get(key) || 0;
fields.set(key, value + 1);

View File

@@ -70,21 +70,21 @@ export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g,
}
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Create lipid params (from martini lipids itp)'
});
parser.addArgument([ '--out', '-o' ], {
parser.add_argument('--out', '-o', {
help: 'Generated lipid params output path, if not given printed to stdout'
});
parser.addArgument([ '--forceDownload', '-f' ], {
action: 'storeTrue',
parser.add_argument('--forceDownload', '-f', {
action: 'store_true',
help: 'Force download of martini lipids itp'
});
interface Args {
out: string
forceDownload: boolean
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
const FORCE_DOWNLOAD = args.forceDownload;

View File

@@ -63,7 +63,7 @@ export function printSecStructure(model: Model) {
const count = residues._rowCount;
let rI = 0;
while (rI < count) {
let start = rI;
const start = rI;
while (rI < count && key[start] === key[rI]) rI++;
rI--;
@@ -230,21 +230,21 @@ async function runFile(filename: string, args: Args) {
}
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Print info about a structure, mainly to test and showcase the mol-model module'
});
parser.addArgument(['--download', '-d'], { help: 'Pdb entry id' });
parser.addArgument(['--file', '-f'], { help: 'filename' });
parser.add_argument('--download', '-d', { help: 'Pdb entry id' });
parser.add_argument('--file', '-f', { help: 'filename' });
parser.addArgument(['--models'], { help: 'print models info', action: 'storeTrue' });
parser.addArgument(['--seq'], { help: 'print sequence', action: 'storeTrue' });
parser.addArgument(['--units'], { help: 'print units', action: 'storeTrue' });
parser.addArgument(['--sym'], { help: 'print symmetry', action: 'storeTrue' });
parser.addArgument(['--rings'], { help: 'print rings', action: 'storeTrue' });
parser.addArgument(['--intraBonds'], { help: 'print intra unit bonds', action: 'storeTrue' });
parser.addArgument(['--interBonds'], { help: 'print inter unit bonds', action: 'storeTrue' });
parser.addArgument(['--mod'], { help: 'print modified residues', action: 'storeTrue' });
parser.addArgument(['--sec'], { help: 'print secoundary structure', action: 'storeTrue' });
parser.add_argument('--models', { help: 'print models info', action: 'store_true' });
parser.add_argument('--seq', { help: 'print sequence', action: 'store_true' });
parser.add_argument('--units', { help: 'print units', action: 'store_true' });
parser.add_argument('--sym', { help: 'print symmetry', action: 'store_true' });
parser.add_argument('--rings', { help: 'print rings', action: 'store_true' });
parser.add_argument('--intraBonds', { help: 'print intra unit bonds', action: 'store_true' });
parser.add_argument('--interBonds', { help: 'print inter unit bonds', action: 'store_true' });
parser.add_argument('--mod', { help: 'print modified residues', action: 'store_true' });
parser.add_argument('--sec', { help: 'print secoundary structure', action: 'store_true' });
interface Args {
download?: string,
file?: string,
@@ -260,7 +260,7 @@ interface Args {
mod?: boolean,
sec?: boolean,
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
if (args.download) runDL(args.download, args);
else if (args.file) runFile(args.file, args);

View File

@@ -38,7 +38,7 @@ function print(volume: Volume) {
}
async function doMesh(volume: Volume, filename: string) {
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) } )).run();
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
// Export the mesh in OBJ format.
@@ -75,13 +75,13 @@ async function run(url: string, meshFilename: string) {
}
const parser = new argparse.ArgumentParser({
addHelp: true,
add_help: true,
description: 'Info about VolumeData from mol-model module'
});
parser.addArgument([ '--emdb', '-e' ], {
parser.add_argument('--emdb', '-e', {
help: 'EMDB id, for example 8116',
});
parser.addArgument([ '--mesh' ], {
parser.add_argument('--mesh', {
help: 'Mesh filename',
required: true
});
@@ -89,6 +89,6 @@ interface Args {
emdb?: string,
mesh: string
}
const args: Args = parser.parseArgs();
const args: Args = parser.parse_args();
run(`https://ds.litemol.org/em/emd-${args.emdb}/cell?detail=4`, args.mesh);

View File

@@ -38,7 +38,7 @@ type MappingRow = Table.Row<S.mapping>;
function writeDomain(enc: CifWriter.Encoder, domain: DomainAnnotation | undefined) {
if (!domain) return;
enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () => CifWriter.Category.ofTable(domain.domains) });
enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () => CifWriter.Category.ofTable(domain.domains) });
enc.writeCategory({ name: `pdbx_${domain.name}_domain_mapping`, instance: () => CifWriter.Category.ofTable(domain.mappings) });
}

View File

@@ -15,7 +15,7 @@ async function getMappings(id: string) {
};
let PORT = process.env.port || 1338;
const PORT = process.env.port || 1338;
const app = express();

View File

@@ -105,7 +105,7 @@ class LightingDemo {
...this.plugin.canvas3d!.props.postprocessing,
...props.postprocessing
},
}});
} });
}
async load({ url, format = 'mmcif', isBinary = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {

View File

@@ -250,7 +250,7 @@ class MolStarProteopediaWrapper {
setBackground(color: number) {
if (!this.plugin.canvas3d) return;
const renderer = this.plugin.canvas3d.props.renderer;
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
}
toggleSpin() {

View File

@@ -5,10 +5,10 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure, StructureElement, StructureProperties } from '../../mol-model/structure';
import { Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
import { Task, RuntimeContext } from '../../mol-task';
import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
import { AccessibleSurfaceAreaParams } from '../../mol-model-props/computed/accessible-surface-area';
import { Vec3 } from '../../mol-math/linear-algebra';
import { getElementMoleculeType } from '../../mol-model/structure/util';
import { MoleculeType } from '../../mol-model/structure/model/types';
@@ -16,6 +16,10 @@ import { AccessibleSurfaceArea } from '../../mol-model-props/computed/accessible
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { MembraneOrientation } from './prop';
const LARGE_CA_THRESHOLD = 5000;
const DEFAULT_UPDATE_INTERVAL = 10;
const LARGE_CA_UPDATE_INTERVAL = 1;
interface ANVILContext {
structure: Structure,
@@ -24,29 +28,45 @@ interface ANVILContext {
minThickness: number,
maxThickness: number,
asaCutoff: number,
adjust: number,
offsets: ArrayLike<number>,
exposed: ArrayLike<boolean>,
exposed: ArrayLike<number>,
hydrophobic: ArrayLike<boolean>,
centroid: Vec3,
extent: number
extent: number,
large: boolean
};
export const ANVILParams = {
numberOfSpherePoints: PD.Numeric(120, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
numberOfSpherePoints: PD.Numeric(175, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),
asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Absolute ASA cutoff above which residues will be considered' })
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1 }, { description: 'Minimum membrane thickness used during refinement' }),
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1 }, { description: 'Maximum membrane thickness used during refinement' }),
asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Relative ASA cutoff above which residues will be considered' }),
adjust: PD.Numeric(14, { min: 0, max: 30, step: 1 }, { description: 'Minimum length of membrane-spanning regions (original values: 14 for alpha-helices and 5 for beta sheets). Set to 0 to not optimize membrane thickness.' }),
tmdetDefinition: PD.Boolean(false, { description: `Use TMDET's classification of membrane-favoring amino acids. TMDET's classification shows better performance on porins and other beta-barrel structures.` })
};
export type ANVILParams = typeof ANVILParams
export type ANVILProps = PD.Values<ANVILParams>
/** ANVIL-specific (not general) definition of membrane-favoring amino acids */
const ANVIL_DEFINITION = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'TRP', 'VAL']);
/** TMDET-specific (not general) definition of membrane-favoring amino acids */
const TMDET_DEFINITION = new Set(['LEU', 'ILE', 'VAL', 'PHE', 'MET', 'GLY', 'TRP', 'TYR']);
/**
* Implements:
* Membrane positioning for high- and low-resolution protein structures through a binary classification approach
* Guillaume Postic, Yassine Ghouzam, Vincent Guiraud, and Jean-Christophe Gelly
* Protein Engineering, Design & Selection, 2015, 15
* doi: 10.1093/protein/gzv063
*
* ANVIL is derived from TMDET, the corresponding classification of hydrophobic amino acids is provided as optional parameter:
* Gabor E. Tusnady, Zsuzsanna Dosztanyi and Istvan Simon
* Transmembrane proteins in the Protein Data Bank: identification and classification
* Bioinformatics, 2004, 2964-2972
* doi: 10.1093/bioinformatics/bth340
*/
export function computeANVIL(structure: Structure, props: ANVILProps) {
return Task.create('Compute Membrane Orientation', async runtime => {
@@ -54,22 +74,38 @@ export function computeANVIL(structure: Structure, props: ANVILProps) {
});
}
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3add = Vec3.add;
const v3clone = Vec3.clone;
const v3create = Vec3.create;
const v3distance = Vec3.distance;
const v3dot = Vec3.dot;
const v3magnitude = Vec3.magnitude;
const v3normalize = Vec3.normalize;
const v3scale = Vec3.scale;
const v3scaleAndAdd = Vec3.scaleAndAdd;
const v3set = Vec3.set;
const v3squaredDistance = Vec3.squaredDistance;
const v3sub = Vec3.sub;
const v3zero = Vec3.zero;
const centroidHelper = new CentroidHelper();
function initialize(structure: Structure, props: ANVILProps): ANVILContext {
async function initialize(structure: Structure, props: ANVILProps, accessibleSurfaceArea: AccessibleSurfaceArea): Promise<ANVILContext> {
const l = StructureElement.Location.create(structure);
const { label_atom_id, x, y, z } = StructureProperties.atom;
const elementCount = structure.polymerResidueCount;
const { label_atom_id, label_comp_id, x, y, z } = StructureProperties.atom;
const asaCutoff = props.asaCutoff / 100;
centroidHelper.reset();
let offsets = new Int32Array(elementCount);
let exposed = new Array<boolean>(elementCount);
const offsets = new Array<number>();
const exposed = new Array<number>();
const hydrophobic = new Array<boolean>();
const definition = props.tmdetDefinition ? TMDET_DEFINITION : ANVIL_DEFINITION;
const accessibleSurfaceArea = structure && AccessibleSurfaceAreaProvider.get(structure);
const asa = accessibleSurfaceArea.value!;
function isPartOfEntity(l: StructureElement.Location): boolean {
return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.valueKind(l.unit.residueIndex[l.element]) === 0;
}
const vec = Vec3();
let m = 0;
const vec = v3zero();
for (let i = 0, il = structure.units.length; i < il; ++i) {
const unit = structure.units[i];
const { elements } = unit;
@@ -79,70 +115,88 @@ function initialize(structure: Structure, props: ANVILProps): ANVILContext {
const eI = elements[j];
l.element = eI;
// consider only amino acids
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein) {
// consider only amino acids in chains
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein || !isPartOfEntity(l)) {
continue;
}
// only CA is considered for downstream operations
if (label_atom_id(l) !== 'CA') {
if (label_atom_id(l) !== 'CA' && label_atom_id(l) !== 'BB') {
continue;
}
// original ANVIL only considers canonical amino acids
if (!MaxAsa[label_comp_id(l)]) {
continue;
}
// while iterating use first pass to compute centroid
Vec3.set(vec, x(l), y(l), z(l));
v3set(vec, x(l), y(l), z(l));
centroidHelper.includeStep(vec);
// keep track of offsets and exposed state to reuse
offsets[m] = structure.serialMapping.getSerialIndex(l.unit, l.element);
exposed[m] = AccessibleSurfaceArea.getValue(l, asa) > props.asaCutoff;
m++;
offsets.push(structure.serialMapping.getSerialIndex(l.unit, l.element));
if (AccessibleSurfaceArea.getValue(l, accessibleSurfaceArea) / MaxAsa[label_comp_id(l)] > asaCutoff) {
exposed.push(structure.serialMapping.getSerialIndex(l.unit, l.element));
hydrophobic.push(isHydrophobic(definition, label_comp_id(l)));
}
}
}
// omit potentially empty tail1
offsets = offsets.slice(0, m);
exposed = exposed.slice(0, m);
// calculate centroid and extent
centroidHelper.finishedIncludeStep();
const centroid = centroidHelper.center;
const centroid = v3clone(centroidHelper.center);
for (let k = 0, kl = offsets.length; k < kl; k++) {
setLocation(l, structure, offsets[k]);
Vec3.set(vec, x(l), y(l), z(l));
v3set(vec, x(l), y(l), z(l));
centroidHelper.radiusStep(vec);
}
const extent = 1.2 * Math.sqrt(centroidHelper.radiusSq);
return {
...props,
structure: structure,
structure,
offsets: offsets,
exposed: exposed,
centroid: centroid,
extent: extent
offsets,
exposed,
hydrophobic,
centroid,
extent,
large: offsets.length > LARGE_CA_THRESHOLD
};
}
export async function calculate(runtime: RuntimeContext, structure: Structure, params: ANVILProps): Promise<MembraneOrientation> {
const { label_comp_id } = StructureProperties.atom;
// can't get away with the default 92 points here
const asaProps = { ...PD.getDefaultValues(AccessibleSurfaceAreaParams), probeSize: 4.0, traceOnly: true, numberOfSpherePoints: 184 };
const accessibleSurfaceArea = await AccessibleSurfaceArea.compute(structure, asaProps).runInContext(runtime);
const ctx = initialize(structure, params);
const initialHphobHphil = HphobHphil.filtered(ctx, label_comp_id);
const ctx = await initialize(structure, params, accessibleSurfaceArea);
const initialHphobHphil = HphobHphil.initial(ctx);
const initialMembrane = findMembrane(ctx, generateSpherePoints(ctx, ctx.numberOfSpherePoints), initialHphobHphil, label_comp_id);
const alternativeMembrane = findMembrane(ctx, findProximateAxes(ctx, initialMembrane), initialHphobHphil, label_comp_id);
const initialMembrane = (await findMembrane(runtime, 'Placing initial membrane...', ctx, generateSpherePoints(ctx, ctx.numberOfSpherePoints), initialHphobHphil))!;
const refinedMembrane = (await findMembrane(runtime, 'Refining membrane placement...', ctx, findProximateAxes(ctx, initialMembrane), initialHphobHphil))!;
let membrane = initialMembrane.qmax! > refinedMembrane.qmax! ? initialMembrane : refinedMembrane;
const membrane = initialMembrane.qmax! > alternativeMembrane.qmax! ? initialMembrane : alternativeMembrane;
if (ctx.adjust && !ctx.large) {
membrane = await adjustThickness(runtime, 'Adjusting membrane thickness...', ctx, membrane, initialHphobHphil);
}
const normalVector = v3zero();
const center = v3zero();
v3sub(normalVector, membrane.planePoint1, membrane.planePoint2);
v3normalize(normalVector, normalVector);
v3add(center, membrane.planePoint1, membrane.planePoint2);
v3scale(center, center, 0.5);
const extent = adjustExtent(ctx, membrane, center);
return {
planePoint1: membrane.planePoint1,
planePoint2: membrane.planePoint2,
normalVector: membrane.normalVector!,
radius: ctx.extent,
centroid: ctx.centroid
normalVector,
centroid: center,
radius: extent
};
}
@@ -160,82 +214,79 @@ namespace MembraneCandidate {
return {
planePoint1: c1,
planePoint2: c2,
stats: stats
stats
};
}
export function scored(spherePoint: Vec3, c1: Vec3, c2: Vec3, stats: HphobHphil, qmax: number, centroid: Vec3): MembraneCandidate {
const diam_vect = Vec3();
Vec3.sub(diam_vect, centroid, spherePoint);
export function scored(spherePoint: Vec3, planePoint1: Vec3, planePoint2: Vec3, stats: HphobHphil, qmax: number, centroid: Vec3): MembraneCandidate {
const normalVector = v3zero();
v3sub(normalVector, centroid, spherePoint);
return {
planePoint1: c1,
planePoint2: c2,
stats: stats,
normalVector: diam_vect,
spherePoint: spherePoint,
qmax: qmax
planePoint1,
planePoint2,
stats,
normalVector,
spherePoint,
qmax
};
}
}
function findMembrane(ctx: ANVILContext, spherePoints: Vec3[], initialStats: HphobHphil, label_comp_id: StructureElement.Property<string>): MembraneCandidate {
const { centroid, stepSize, minThickness, maxThickness } = ctx;
async function findMembrane(runtime: RuntimeContext, message: string | undefined, ctx: ANVILContext, spherePoints: Vec3[], initialStats: HphobHphil): Promise<MembraneCandidate | undefined> {
const { centroid, stepSize, minThickness, maxThickness, large } = ctx;
// best performing membrane
let membrane: MembraneCandidate;
let membrane: MembraneCandidate | undefined;
// score of the best performing membrane
let qmax = 0;
// construct slices of thickness 1.0 along the axis connecting the centroid and the spherePoint
const diam = Vec3();
for (let i = 0, il = spherePoints.length; i < il; i++) {
const spherePoint = spherePoints[i];
Vec3.sub(diam, centroid, spherePoint);
Vec3.scale(diam, diam, 2);
const diamNorm = Vec3.magnitude(diam);
const qvartemp = [];
const diam = v3zero();
for (let n = 0, nl = spherePoints.length; n < nl; n++) {
if (runtime.shouldUpdate && message && (n + 1) % (large ? LARGE_CA_UPDATE_INTERVAL : DEFAULT_UPDATE_INTERVAL) === 0) {
await runtime.update({ message, current: (n + 1), max: nl });
}
const spherePoint = spherePoints[n];
v3sub(diam, centroid, spherePoint);
v3scale(diam, diam, 2);
const diamNorm = v3magnitude(diam);
const sliceStats = HphobHphil.sliced(ctx, stepSize, spherePoint, diam, diamNorm);
const qvartemp = [];
for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
const c1 = Vec3();
const c2 = Vec3();
Vec3.scaleAndAdd(c1, spherePoint, diam, i / diamNorm);
Vec3.scaleAndAdd(c2, spherePoint, diam, (i + stepSize) / diamNorm);
const c1 = v3zero();
const c2 = v3zero();
v3scaleAndAdd(c1, spherePoint, diam, i / diamNorm);
v3scaleAndAdd(c2, spherePoint, diam, (i + stepSize) / diamNorm);
// evaluate how well this membrane slice embeddeds the peculiar residues
const stats = HphobHphil.filtered(ctx, label_comp_id, (testPoint: Vec3) => isInMembranePlane(testPoint, diam, c1, c2));
const stats = sliceStats[Math.round(i / stepSize)];
qvartemp.push(MembraneCandidate.initial(c1, c2, stats));
}
let jmax = (minThickness / stepSize) - 1;
for (let width = 0, widthl = maxThickness; width < widthl;) {
const imax = qvartemp.length - 1 - jmax;
for (let i = 0, il = imax; i < il; i++) {
const c1 = qvartemp[i].planePoint1;
const c2 = qvartemp[i + jmax].planePoint2;
let jmax = Math.floor((minThickness / stepSize) - 1);
for (let width = 0, widthl = maxThickness; width <= widthl;) {
for (let i = 0, il = qvartemp.length - 1 - jmax; i < il; i++) {
let hphob = 0;
let hphil = 0;
let total = 0;
for (let j = 0; j < jmax; j++) {
const ij = qvartemp[i + j];
if (j === 0 || j === jmax - 1) {
hphob += 0.5 * ij.stats.hphob;
hphob += Math.floor(0.5 * ij.stats.hphob);
hphil += 0.5 * ij.stats.hphil;
} else {
hphob += ij.stats.hphob;
hphil += ij.stats.hphil;
}
total += ij.stats.total;
}
const stats = HphobHphil.of(hphob, hphil, total);
if (hphob !== 0) {
const stats = { hphob, hphil };
const qvaltest = qValue(stats, initialStats);
if (qvaltest > qmax) {
if (qvaltest >= qmax) {
qmax = qvaltest;
membrane = MembraneCandidate.scored(spherePoint, c1, c2, HphobHphil.of(hphob, hphil, total), qmax, centroid);
membrane = MembraneCandidate.scored(spherePoint, qvartemp[i].planePoint1, qvartemp[i + jmax].planePoint2, stats, qmax, centroid);
}
}
}
@@ -244,15 +295,192 @@ function findMembrane(ctx: ANVILContext, spherePoints: Vec3[], initialStats: Hph
}
}
return membrane!;
return membrane;
}
/** Adjust membrane thickness by maximizing the number of membrane segments. */
async function adjustThickness(runtime: RuntimeContext, message: string | undefined, ctx: ANVILContext, membrane: MembraneCandidate, initialHphobHphil: HphobHphil): Promise<MembraneCandidate> {
const { minThickness, large } = ctx;
const step = 0.3;
let maxThickness = v3distance(membrane.planePoint1, membrane.planePoint2);
let maxNos = membraneSegments(ctx, membrane).length;
let optimalThickness = membrane;
let n = 0;
const nl = Math.ceil((maxThickness - minThickness) / step);
while (maxThickness > minThickness) {
n++;
if (runtime.shouldUpdate && message && n % (large ? LARGE_CA_UPDATE_INTERVAL : DEFAULT_UPDATE_INTERVAL) === 0) {
await runtime.update({ message, current: n, max: nl });
}
const p = {
...ctx,
maxThickness,
stepSize: step
};
const temp = await findMembrane(runtime, void 0, p, [membrane.spherePoint!], initialHphobHphil);
if (temp) {
const nos = membraneSegments(ctx, temp).length;
if (nos > maxNos) {
maxNos = nos;
optimalThickness = temp;
}
}
maxThickness -= step;
}
return optimalThickness;
}
/** Report auth_seq_ids for all transmembrane segments. Will reject segments that are shorter than the adjust parameter specifies. Missing residues are considered in-membrane. */
function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): ArrayLike<{ start: number, end: number }> {
const { offsets, structure, adjust } = ctx;
const { normalVector, planePoint1, planePoint2 } = membrane;
const { units } = structure;
const { elementIndices, unitIndices } = structure.serialMapping;
const testPoint = v3zero();
const { auth_seq_id } = StructureProperties.residue;
const d1 = -v3dot(normalVector!, planePoint1);
const d2 = -v3dot(normalVector!, planePoint2);
const dMin = Math.min(d1, d2);
const dMax = Math.max(d1, d2);
const inMembrane: { [k: string]: Set<number> } = Object.create(null);
const outMembrane: { [k: string]: Set<number> } = Object.create(null);
const segments: Array<{ start: number, end: number }> = [];
let authAsymId;
let lastAuthAsymId = null;
let authSeqId;
let lastAuthSeqId = units[0].model.atomicHierarchy.residues.auth_seq_id.value((units[0] as Unit.Atomic).chainIndex[0]) - 1;
let startOffset = 0;
let endOffset = 0;
// collect all residues in membrane layer
for (let k = 0, kl = offsets.length; k < kl; k++) {
const unit = units[unitIndices[offsets[k]]];
if (!Unit.isAtomic(unit)) notAtomic();
const elementIndex = elementIndices[offsets[k]];
authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
if (authAsymId !== lastAuthAsymId) {
if (!inMembrane[authAsymId]) inMembrane[authAsymId] = new Set<number>();
if (!outMembrane[authAsymId]) outMembrane[authAsymId] = new Set<number>();
lastAuthAsymId = authAsymId;
}
authSeqId = unit.model.atomicHierarchy.residues.auth_seq_id.value(unit.residueIndex[elementIndex]);
v3set(testPoint, unit.conformation.x(elementIndex), unit.conformation.y(elementIndex), unit.conformation.z(elementIndex));
if (_isInMembranePlane(testPoint, normalVector!, dMin, dMax)) {
inMembrane[authAsymId].add(authSeqId);
} else {
outMembrane[authAsymId].add(authSeqId);
}
}
for (let k = 0, kl = offsets.length; k < kl; k++) {
const unit = units[unitIndices[offsets[k]]];
if (!Unit.isAtomic(unit)) notAtomic();
const elementIndex = elementIndices[offsets[k]];
authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
authSeqId = unit.model.atomicHierarchy.residues.auth_seq_id.value(unit.residueIndex[elementIndex]);
if (inMembrane[authAsymId].has(authSeqId)) {
// chain change
if (authAsymId !== lastAuthAsymId) {
segments.push({ start: startOffset, end: endOffset });
lastAuthAsymId = authAsymId;
startOffset = k;
endOffset = k;
}
// sequence gaps
if (authSeqId !== lastAuthSeqId + 1) {
if (outMembrane[authAsymId].has(lastAuthSeqId + 1)) {
segments.push({ start: startOffset, end: endOffset });
startOffset = k;
}
lastAuthSeqId = authSeqId;
endOffset = k;
} else {
lastAuthSeqId++;
endOffset++;
}
}
}
segments.push({ start: startOffset, end: endOffset });
const l = StructureElement.Location.create(structure);
let startAuth;
let endAuth;
const refinedSegments: Array<{ start: number, end: number }> = [];
for (let k = 0, kl = segments.length; k < kl; k++) {
const { start, end } = segments[k];
if (start === 0 || end === offsets.length - 1) continue;
// evaluate residues 1 pos outside of membrane
setLocation(l, structure, offsets[start - 1]);
v3set(testPoint, l.unit.conformation.x(l.element), l.unit.conformation.y(l.element), l.unit.conformation.z(l.element));
const d3 = -v3dot(normalVector!, testPoint);
setLocation(l, structure, offsets[end + 1]);
v3set(testPoint, l.unit.conformation.x(l.element), l.unit.conformation.y(l.element), l.unit.conformation.z(l.element));
const d4 = -v3dot(normalVector!, testPoint);
if (Math.min(d3, d4) < dMin && Math.max(d3, d4) > dMax) {
// reject this refinement
setLocation(l, structure, offsets[start]);
startAuth = auth_seq_id(l);
setLocation(l, structure, offsets[end]);
endAuth = auth_seq_id(l);
if (Math.abs(startAuth - endAuth) + 1 < adjust) {
return [];
}
refinedSegments.push(segments[k]);
}
}
return refinedSegments;
}
function notAtomic(): never {
throw new Error('Property only available for atomic models.');
}
/** Filter for membrane residues and calculate the final extent of the membrane layer */
function adjustExtent(ctx: ANVILContext, membrane: MembraneCandidate, centroid: Vec3): number {
const { offsets, structure } = ctx;
const { normalVector, planePoint1, planePoint2 } = membrane;
const l = StructureElement.Location.create(structure);
const testPoint = v3zero();
const { x, y, z } = StructureProperties.atom;
const d1 = -v3dot(normalVector!, planePoint1);
const d2 = -v3dot(normalVector!, planePoint2);
const dMin = Math.min(d1, d2);
const dMax = Math.max(d1, d2);
let extent = 0;
for (let k = 0, kl = offsets.length; k < kl; k++) {
setLocation(l, structure, offsets[k]);
v3set(testPoint, x(l), y(l), z(l));
if (_isInMembranePlane(testPoint, normalVector!, dMin, dMax)) {
const dsq = v3squaredDistance(testPoint, centroid);
if (dsq > extent) extent = dsq;
}
}
return Math.sqrt(extent);
}
function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
if(initialStats.hphob < 1) {
if (initialStats.hphob < 1) {
initialStats.hphob = 0.1;
}
if(initialStats.hphil < 1) {
if (initialStats.hphil < 1) {
initialStats.hphil += 1;
}
@@ -262,23 +490,27 @@ function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
}
export function isInMembranePlane(testPoint: Vec3, normalVector: Vec3, planePoint1: Vec3, planePoint2: Vec3): boolean {
const d1 = -Vec3.dot(normalVector, planePoint1);
const d2 = -Vec3.dot(normalVector, planePoint2);
const d = -Vec3.dot(normalVector, testPoint);
return d > Math.min(d1, d2) && d < Math.max(d1, d2);
const d1 = -v3dot(normalVector, planePoint1);
const d2 = -v3dot(normalVector, planePoint2);
return _isInMembranePlane(testPoint, normalVector, Math.min(d1, d2), Math.max(d1, d2));
}
// generates a defined number of points on a sphere with radius = extent around the specified centroid
function _isInMembranePlane(testPoint: Vec3, normalVector: Vec3, min: number, max: number): boolean {
const d = -v3dot(normalVector, testPoint);
return d > min && d < max;
}
/** Generates a defined number of points on a sphere with radius = extent around the specified centroid */
function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number): Vec3[] {
const { centroid, extent } = ctx;
const points = [];
let oldPhi = 0, h, theta, phi;
for(let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
h = -1 + 2 * (k - 1) / (numberOfSpherePoints - 1);
for (let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
h = -1 + 2 * (k - 1) / (2 * numberOfSpherePoints - 1);
theta = Math.acos(h);
phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(2 * numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
const point = Vec3.create(
const point = v3create(
extent * Math.sin(phi) * Math.sin(theta) + centroid[0],
extent * Math.cos(theta) + centroid[1],
extent * Math.cos(phi) * Math.sin(theta) + centroid[2]
@@ -290,18 +522,18 @@ function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number):
return points;
}
// generates sphere points close to that of the initial membrane
/** Generates sphere points close to that of the initial membrane */
function findProximateAxes(ctx: ANVILContext, membrane: MembraneCandidate): Vec3[] {
const { numberOfSpherePoints, extent } = ctx;
const points = generateSpherePoints(ctx, 30000);
let j = 4;
let sphere_pts2: Vec3[] = [];
const s = 2 * extent / numberOfSpherePoints;
while (sphere_pts2.length < numberOfSpherePoints) {
const d = 2 * extent / numberOfSpherePoints + j;
const dsq = d * d;
const dsq = (s + j) * (s + j);
sphere_pts2 = [];
for (let i = 0, il = points.length; i < il; i++) {
if (Vec3.squaredDistance(points[i], membrane.spherePoint!) < dsq) {
if (v3squaredDistance(points[i], membrane.spherePoint!) < dsq) {
sphere_pts2.push(points[i]);
}
}
@@ -312,56 +544,78 @@ function findProximateAxes(ctx: ANVILContext, membrane: MembraneCandidate): Vec3
interface HphobHphil {
hphob: number,
hphil: number,
total: number
hphil: number
}
namespace HphobHphil {
export function of(hphob: number, hphil: number, total?: number) {
return {
hphob: hphob,
hphil: hphil,
total: !!total ? total : hphob + hphil
};
}
const testPoint = Vec3();
export function filtered(ctx: ANVILContext, label_comp_id: StructureElement.Property<string>, filter?: (test: Vec3) => boolean): HphobHphil {
const { offsets, exposed, structure } = ctx;
const l = StructureElement.Location.create(structure);
const { x, y, z } = StructureProperties.atom;
export function initial(ctx: ANVILContext): HphobHphil {
const { exposed, hydrophobic } = ctx;
let hphob = 0;
let hphil = 0;
for (let k = 0, kl = offsets.length; k < kl; k++) {
// ignore buried residues
if (!exposed[k]) {
continue;
}
setLocation(l, structure, offsets[k]);
Vec3.set(testPoint, x(l), y(l), z(l));
// testPoints have to be in putative membrane layer
if (filter && !filter(testPoint)) {
continue;
}
if (isHydrophobic(label_comp_id(l))) {
for (let k = 0, kl = exposed.length; k < kl; k++) {
if (hydrophobic[k]) {
hphob++;
} else {
hphil++;
}
}
return of(hphob, hphil);
return { hphob, hphil };
}
const testPoint = v3zero();
export function sliced(ctx: ANVILContext, stepSize: number, spherePoint: Vec3, diam: Vec3, diamNorm: number): HphobHphil[] {
const { exposed, hydrophobic, structure } = ctx;
const { units, serialMapping } = structure;
const { unitIndices, elementIndices } = serialMapping;
const sliceStats: HphobHphil[] = [];
for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
sliceStats[sliceStats.length] = { hphob: 0, hphil: 0 };
}
for (let i = 0, il = exposed.length; i < il; i++) {
const unit = units[unitIndices[exposed[i]]];
const elementIndex = elementIndices[exposed[i]];
v3set(testPoint, unit.conformation.x(elementIndex), unit.conformation.y(elementIndex), unit.conformation.z(elementIndex));
v3sub(testPoint, testPoint, spherePoint);
if (hydrophobic[i]) {
sliceStats[Math.floor(v3dot(testPoint, diam) / diamNorm / stepSize)].hphob++;
} else {
sliceStats[Math.floor(v3dot(testPoint, diam) / diamNorm / stepSize)].hphil++;
}
}
return sliceStats;
}
}
// ANVIL-specific (not general) definition of membrane-favoring amino acids
const HYDROPHOBIC_AMINO_ACIDS = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'THR', 'VAL']);
export function isHydrophobic(label_comp_id: string): boolean {
return HYDROPHOBIC_AMINO_ACIDS.has(label_comp_id);
/** Returns true if the definition considers this as membrane-favoring amino acid */
export function isHydrophobic(definition: Set<string>, label_comp_id: string): boolean {
return definition.has(label_comp_id);
}
/** Accessible surface area used for normalization. ANVIL uses 'Total-Side REL' values from NACCESS, from: Hubbard, S. J., & Thornton, J. M. (1993). naccess. Computer Program, Department of Biochemistry and Molecular Biology, University College London, 2(1). */
export const MaxAsa: { [k: string]: number } = {
'ALA': 69.41,
'ARG': 201.25,
'ASN': 106.24,
'ASP': 102.69,
'CYS': 96.75,
'GLU': 134.74,
'GLN': 140.99,
'GLY': 32.33,
'HIS': 147.08,
'ILE': 137.96,
'LEU': 141.12,
'LYS': 163.30,
'MET': 156.64,
'PHE': 164.11,
'PRO': 119.90,
'SER': 78.11,
'THR': 101.70,
'TRP': 211.26,
'TYR': 177.38,
'VAL': 114.28
};
function setLocation(l: StructureElement.Location, structure: Structure, serialIndex: number) {
l.structure = structure;
l.unit = structure.units[structure.serialMapping.unitIndices[serialIndex]];

View File

@@ -52,7 +52,7 @@ export const ANVILMembraneOrientation = PluginBehavior.create<{ autoAttach: bool
}
update(p: { autoAttach: boolean }) {
let updated = this.params.autoAttach !== p.autoAttach;
const updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
return updated;
@@ -150,7 +150,7 @@ export const MembraneOrientationPreset = StructureRepresentationPresetProvider({
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
if (!MembraneOrientationProvider.get(structure).value) {

View File

@@ -11,7 +11,6 @@ import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
import { ANVILParams, ANVILProps, computeANVIL, isInMembranePlane } from './algorithm';
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
import { Vec3 } from '../../mol-math/linear-algebra';
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
import { CustomPropSymbol } from '../../mol-script/language/symbol';
@@ -76,7 +75,6 @@ export const MembraneOrientationProvider: CustomStructureProperty.Provider<Membr
});
async function computeAnvil(ctx: CustomProperty.Context, data: Structure, props: Partial<ANVILProps>): Promise<MembraneOrientation> {
await AccessibleSurfaceAreaProvider.attach(ctx, data);
const p = { ...PD.getDefaultValues(ANVILParams), ...props };
return await computeANVIL(data, p).runInContext(ctx.runtime);
}

View File

@@ -9,7 +9,6 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
import { Structure } from '../../mol-model/structure';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
import { MembraneOrientation } from './prop';
import { ThemeRegistryContext } from '../../mol-theme/theme';
@@ -30,18 +29,9 @@ import { CustomProperty } from '../../mol-model-props/common/custom-property';
const SharedParams = {
color: PD.Color(ColorNames.lightgrey),
radiusFactor: PD.Numeric(0.8333, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
radiusFactor: PD.Numeric(1.2, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
};
const BilayerSpheresParams = {
...Spheres.Params,
...SharedParams,
sphereSize: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }, { description: 'Size of spheres that represent membrane planes' }),
density: PD.Numeric(1, { min: 0.25, max: 10, step: 0.25 }, { description: 'Distance between spheres'})
};
export type BilayerSpheresParams = typeof BilayerSpheresParams
export type BilayerSpheresProps = PD.Values<BilayerSpheresParams>
const BilayerPlanesParams = {
...Mesh.Params,
...SharedParams,
@@ -66,7 +56,6 @@ const MembraneOrientationVisuals = {
};
export const MembraneOrientationParams = {
...BilayerSpheresParams,
...BilayerPlanesParams,
...BilayerRimsParams,
visuals: PD.MultiSelect(['bilayer-planes', 'bilayer-rims'], PD.objectToOptions(MembraneOrientationVisuals)),
@@ -104,16 +93,16 @@ function membraneLabel(data: Structure) {
}
function getBilayerRims(ctx: RuntimeContext, data: Structure, props: BilayerRimsProps, shape?: Shape<Lines>): Shape<Lines> {
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
const { planePoint1: p1, planePoint2: p2, centroid, radius } = MembraneOrientationProvider.get(data).value!;
const scaledRadius = props.radiusFactor * radius;
const builder = LinesBuilder.create(128, 64, shape?.geometry);
getLayerCircle(builder, p1, centroid, normal, scaledRadius, props);
getLayerCircle(builder, p2, centroid, normal, scaledRadius, props);
getLayerCircle(builder, p1, centroid, scaledRadius, props);
getLayerCircle(builder, p2, centroid, scaledRadius, props);
return Shape.create('Bilayer rims', data, builder.getLines(), () => props.color, () => props.linesSize, () => membraneLabel(data));
}
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
const circle = getCircle(p, centroid, normal, radius);
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
const circle = getCircle(p, centroid, radius);
const { indices, vertices } = circle;
for (let j = 0, jl = indices.length; j < jl; j += 3) {
if (props.dashedLines && j % 2 === 1) continue; // draw every other segment to get dashes
@@ -130,8 +119,13 @@ function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal:
}
const tmpMat = Mat4();
function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
Mat4.targetTo(tmpMat, p, centroid, normal);
const tmpV = Vec3();
function getCircle(p: Vec3, centroid: Vec3, radius: number) {
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, p, centroid)) === 0) {
Mat4.targetTo(tmpMat, p, centroid, Vec3.unitY);
} else {
Mat4.targetTo(tmpMat, p, centroid, Vec3.unitX);
}
Mat4.setTranslation(tmpMat, p);
Mat4.mul(tmpMat, tmpMat, Mat4.rotX90);
@@ -140,16 +134,16 @@ function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
}
function getBilayerPlanes(ctx: RuntimeContext, data: Structure, props: BilayerPlanesProps, shape?: Shape<Mesh>): Shape<Mesh> {
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
const { planePoint1: p1, planePoint2: p2, centroid, radius } = MembraneOrientationProvider.get(data).value!;
const state = MeshBuilder.createState(128, 64, shape && shape.geometry);
const scaledRadius = props.radiusFactor * radius;
getLayerPlane(state, p1, centroid, normal, scaledRadius);
getLayerPlane(state, p2, centroid, normal, scaledRadius);
getLayerPlane(state, p1, centroid, scaledRadius);
getLayerPlane(state, p2, centroid, scaledRadius);
return Shape.create('Bilayer planes', data, MeshBuilder.getMesh(state), () => props.color, () => 1, () => membraneLabel(data));
}
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
const circle = getCircle(p, centroid, normal, radius);
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, radius: number) {
const circle = getCircle(p, centroid, radius);
state.currentGroup = 0;
MeshBuilder.addPrimitive(state, Mat4.id, circle);
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -33,7 +33,7 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
if (ctx.structure && info) {
const colors = distinctColors(info.packingsCount);
let hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number];
@@ -48,7 +48,7 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
}
}}, { minLabel: 'Min', maxLabel: 'Max' });
} }, { minLabel: 'Min', maxLabel: 'Max' });
legend = palette.legend;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Ludovic Autin <autin@scripps.edu>
* @author Ludovic Autin <ludovic.autin@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -49,7 +49,7 @@ function ResampleControlPoints(points: NumberArray, segmentLength: number) {
// controlPoints.Insert(0, controlPoints[0] + (controlPoints[0] - controlPoints[1]) / 2.0f);
// controlPoints.Add(controlPoints[nP - 1] + (controlPoints[nP - 1] - controlPoints[nP - 2]) / 2.0f);
let resampledControlPoints: Vec3[] = [];
const resampledControlPoints: Vec3[] = [];
// resampledControlPoints.Add(controlPoints[0]);
// resampledControlPoints.Add(controlPoints[1]);
@@ -111,7 +111,7 @@ function GetSmoothNormals(points: Vec3[]) {
let p1 = points[1];
let p2 = points[2];
const p21 = Vec3.sub(tmpV1, p2, p1);
const p01 = Vec3.sub(tmpV2, p0, p1);
const p01 = Vec3.sub(tmpV2, p0, p1);
const p0121 = Vec3.cross(tmpV3, p01, p21);
Vec3.normalize(prevV, p0121);
smoothNormals.push(Vec3.clone(prevV));
@@ -179,7 +179,7 @@ function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t));
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t);
// # compute reflection vector of R_2
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
const c2 = Vec3.dot(v2, v2);
// compute r_(i+1) = R_2 * r_i^L
const v2l = Vec3.scale(mfTmpV8, v1, (2.0 / c2) * Vec3.dot(v2, ref_L_i));
@@ -195,7 +195,7 @@ export function getMatFromResamplePoints(points: NumberArray, segmentLength: num
let new_points: Vec3[] = [];
if (resample) new_points = ResampleControlPoints(points, segmentLength);
else {
for (let idx = 0; idx < points.length / 3; ++idx){
for (let idx = 0; idx < points.length / 3; ++idx) {
new_points.push(Vec3.fromArray(Vec3.zero(), points, idx * 3));
}
}
@@ -211,7 +211,7 @@ export function getMatFromResamplePoints(points: NumberArray, segmentLength: num
if (d >= segmentLength) {
// use twist or random?
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t); // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60 ); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)); // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
// let pos:Vec3 = Vec3.add(Vec3.zero(),pti1,pti)
// pos = Vec3.scale(pos,pos,1.0/2.0);

View File

@@ -13,16 +13,27 @@ export interface CellPack {
export interface CellPacking {
name: string,
location: 'surface' | 'interior' | 'cytoplasme',
location: 'surface' | 'interior' | 'cytoplasme'
ingredients: Packing['ingredients']
compartment?: CellCompartment
}
//
export interface CellCompartment {
filename?: string
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
compartment_primitives?: CompartmentPrimitives
}
export interface Cell {
recipe: Recipe
options?: RecipeOptions
cytoplasme?: Packing
compartments?: { [key: string]: Compartment }
mapping_ids?: { [key: number]: [number, string] }
}
export interface RecipeOptions {
resultfile?: string
}
export interface Recipe {
@@ -35,8 +46,29 @@ export interface Recipe {
export interface Compartment {
surface?: Packing
interior?: Packing
geom?: unknown
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
mb?: CompartmentPrimitives
}
// Primitives discribing a compartment
export const enum CompartmentPrimitiveType {
MetaBall = 0,
Sphere = 1,
Cube = 2,
Cylinder = 3,
Cone = 4,
Plane = 5,
None = 6
}
export interface CompartmentPrimitives{
positions?: number[];
radii?: number[];
types?: CompartmentPrimitiveType[];
}
export interface Packing {
ingredients: { [key: string]: Ingredient }
}
@@ -64,18 +96,20 @@ export interface Ingredient {
[curveX: string]: unknown;
/** the orientation in the membrane */
principalAxis?: Vec3;
principalVector?: Vec3;
/** offset along membrane */
offset?: Vec3;
ingtype?: string;
color?: Vec3;
confidence?: number;
Type?: string;
}
export interface IngredientSource {
pdb: string;
bu?: string; /** biological unit e.g AU,BU1,etc.. */
bu?: string; /** biological unit e.g AU,BU1,etc.. */
selection?: string; /** NGL selection or :A or :B etc.. */
model?: string; /** model number e.g 0,1,2... */
model?: string; /** model number e.g 0,1,2... */
transform: {
center: boolean;
translate?: Vec3;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/

View File

@@ -2,13 +2,14 @@
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
import { PluginContext } from '../../mol-plugin/context';
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Ingredient, IngredientSource, CellPacking } from './data';
import { Ingredient, CellPacking, CompartmentPrimitives } from './data';
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
@@ -17,7 +18,7 @@ import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
import { SymmetryOperator } from '../../mol-math/geometry';
import { Task, RuntimeContext } from '../../mol-task';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { getMatFromResamplePoints } from './curve';
import { compile } from '../../mol-script/runtime/query/compiler';
@@ -28,8 +29,9 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser';
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { readFromFile } from '../../mol-util/data-source';
import { objectForEach } from '../../mol-util/object';
import { readFromFile } from '../../mol-util/data-source';
import { ColorNames } from '../../mol-util/color/names';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/results/${fileName}`;
@@ -41,12 +43,16 @@ class TrajectoryCache {
get(id: string) { return this.map.get(id); }
}
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
baseUrl: string, trajCache: TrajectoryCache, location: string,
file?: Asset.File
) {
const assetManager = plugin.managers.asset;
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
if (location === 'surface') surface = true;
let trajectory = trajCache.get(id);
let assets: Asset.Wrapper[] = [];
const assets: Asset.Wrapper[] = [];
if (!trajectory) {
if (file) {
if (file.name.endsWith('.cif')) {
@@ -68,10 +74,11 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
throw new Error(`unsupported file type '${file.name}'`);
}
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
if (surface){
if (surface) {
try {
const data = await getFromOPM(plugin, id, assetManager);
assets.push(data.asset);
data.pdb.id! = id.toUpperCase();
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
} catch (e) {
// fallback to getFromPdb
@@ -100,7 +107,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
return { model, assets };
}
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
let structure = Structure.ofModel(model);
const { assembly } = props;
@@ -108,11 +115,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
}
let query;
if (source.selection){
const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
if (source.source.selection) {
const sel = source.source.selection;
// selection can have the model ID as well. remove it
const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
query = MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
})
]);
@@ -123,11 +131,11 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
})
]);
}
const compiled = compile<StructureSelection>(query);
const result = compiled(new QueryContext(structure));
structure = StructureSelection.unionStructure(result);
// change here if possible the label ?
// structure.label = source.name;
return structure;
}
@@ -141,9 +149,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) {
}
function getTransform(trans: Vec3, rot: Quat) {
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
Mat4.setTranslation(m, p);
return m;
}
@@ -157,9 +165,9 @@ function getCurveTransforms(ingredient: Ingredient) {
const n = ingredient.nbCurve || 0;
const instances: Mat4[] = [];
let segmentLength = 3.4;
if (ingredient.uLength){
if (ingredient.uLength) {
segmentLength = ingredient.uLength;
} else if (ingredient.radii){
} else if (ingredient.radii) {
segmentLength = ingredient.radii[0].radii
? ingredient.radii[0].radii[0] * 2.0
: 3.4;
@@ -168,7 +176,7 @@ function getCurveTransforms(ingredient: Ingredient) {
for (let i = 0; i < n; ++i) {
const cname = `curve${i}`;
if (!(cname in ingredient)) {
// console.warn(`Expected '${cname}' in ingredient`)
console.warn(`Expected '${cname}' in ingredient`);
continue;
}
const _points = ingredient[cname] as Vec3[];
@@ -177,9 +185,9 @@ function getCurveTransforms(ingredient: Ingredient) {
continue;
}
// test for resampling
let distance: number = Vec3.distance(_points[0], _points[1]);
const distance: number = Vec3.distance(_points[0], _points[1]);
if (distance >= segmentLength + 2.0) {
console.info(distance);
// console.info(distance);
resampling = true;
}
const points = new Float32Array(_points.length * 3);
@@ -190,13 +198,13 @@ function getCurveTransforms(ingredient: Ingredient) {
return instances;
}
function getAssembly(transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder();
function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder({ label: name });
const { units } = structure;
for (let i = 0, il = transforms.length; i < il; ++i) {
const id = `${i + 1}`;
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } });
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [id] } });
for (const unit of units) {
builder.addWithOperator(unit, op);
}
@@ -307,13 +315,13 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
});
const curveModel = await plugin.runTask(curveModelTask);
return getStructure(plugin, curveModel, ingredient.source);
// ingredient.source.selection = undefined;
return getStructure(plugin, curveModel, ingredient);
}
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
const { name, source, results, nbCurve } = ingredient;
if (source.pdb === 'None') return;
const file = ingredientFiles[source.pdb];
if (!file) {
// TODO can these be added to the library?
@@ -325,72 +333,79 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
}
// model id in case structure is NMR
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
if (!model) return;
let structure: Structure;
if (nbCurve) {
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
} else {
if ((!results || results.length === 0)) return;
let bu: string|undefined = source.bu ? source.bu : undefined;
if (bu){
if (bu) {
if (bu === 'AU') {
bu = undefined;
} else {
bu = bu.slice(2);
}
}
structure = await getStructure(plugin, model, source, { assembly: bu });
structure = await getStructure(plugin, model, ingredient, { assembly: bu });
// transform with offset and pcp
let legacy: boolean = true;
if (ingredient.offset || ingredient.principalAxis){
const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
if (pcp) {
legacy = false;
const structureMean = getStructureMean(structure);
Vec3.negate(structureMean, structureMean);
const m1: Mat4 = Mat4.identity();
Mat4.setTranslation(m1, structureMean);
structure = Structure.transform(structure, m1);
if (ingredient.offset){
if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())){
if (ingredient.offset) {
const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ??
if (location !== 'surface') {
Vec3.negate(o, o);
}
const m: Mat4 = Mat4.identity();
Mat4.setTranslation(m, ingredient.offset);
Mat4.setTranslation(m, o);
structure = Structure.transform(structure, m);
}
}
if (ingredient.principalAxis){
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)){
if (pcp) {
const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
if (!Vec3.exactEquals(p, Vec3.unitZ)) {
const q: Quat = Quat.identity();
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
Quat.rotationTo(q, p, Vec3.unitZ);
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
structure = Structure.transform(structure, m);
}
}
}
structure = getAssembly(getResultTransforms(results, legacy), structure);
structure = getAssembly(name, getResultTransforms(results, legacy), structure);
}
return { structure, assets };
}
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
return Task.create('Create Packing Structure', async ctx => {
const { ingredients, name } = packing;
const { ingredients, location, name } = packing;
const assets: Asset.Wrapper[] = [];
const trajCache = new TrajectoryCache();
const structures: Structure[] = [];
const colors: Color[] = [];
let skipColors: boolean = false;
for (const iName in ingredients) {
if (ctx.shouldUpdate) await ctx.update(iName);
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
if (ingredientStructure) {
structures.push(ingredientStructure.structure);
assets.push(...ingredientStructure.assets);
const c = ingredients[iName].color;
if (c){
if (c) {
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
} else {
skipColors = true;
colors.push(Color.fromNormalizedRgb(1, 0, 0));
}
}
}
@@ -402,7 +417,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
let maxInvariantId = 0;
let maxChainGroupId = 0;
const maxChainGroupId = 0;
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId;
const chainGroupId = u.chainGroupId + offsetChainGroupId;
@@ -414,21 +429,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = Structure.create(units);
for( let i = 0, il = structure.models.length; i < il; ++i) {
const structure = Structure.create(units, { label: name + '.' + location });
for (let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
return { structure, assets, colors: skipColors ? undefined : colors };
return { structure, assets, colors: colors };
});
}
async function handleHivRna(plugin: PluginContext, 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') {
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
const points = json.data.points as number[];
const curve0: Vec3[] = [];
for (let j = 0, jl = points.length; j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j));
@@ -454,7 +468,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
break;
}
}
if (!file){
if (!file) {
// check for cif directly
const cifileName = `${name}.cif`;
for (const f of params.ingredients) {
@@ -465,7 +479,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
}
}
}
let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
let geometry_membrane: boolean = false; // membrane can be a mesh geometry
let b = state.build().toRoot();
if (file) {
if (file.name.endsWith('.cif')) {
@@ -474,27 +489,82 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
}
} else {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
if (name.toLowerCase().endsWith('.bcif')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
} else if (name.toLowerCase().endsWith('.cif')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
} else if (name.toLowerCase().endsWith('.ply')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
geometry_membrane = true;
} else {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
legacy_membrane = true;
}
}
const membrane = await b.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(StructureFromAssemblies, undefined, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
representation: params.preset.representation,
const props = {
type: {
name: 'assembly' as const,
params: { id: '1' }
}
};
if (legacy_membrane) {
// old membrane
const membrane = await b.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(StructureFromAssemblies, undefined, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
representation: params.preset.representation,
};
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
} else if (geometry_membrane) {
await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ShapeFromPly)
.apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
.commit({ revertOnError: true });
} else {
const membrane = await b.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, props, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
representation: params.preset.representation,
};
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
}
}
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
const nSpheres = primitives.positions!.length / 3;
// console.log('ok mb ', nSpheres);
// TODO : take in account the type of the primitives.
for (let j = 0; j < nSpheres; j++) {
await state.build()
.toRoot()
.apply(CreateCompartmentSphere, {
center: Vec3.create(
primitives.positions![j * 3 + 0],
primitives.positions![j * 3 + 1],
primitives.positions![j * 3 + 2]
),
radius: primitives!.radii![j]
})
.commit();
}
}
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
const ingredientFiles = params.ingredients || [];
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
let resultsFile: Asset.File | null = params.results;
if (params.source.name === 'id') {
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
cellPackJson = state.build().toRoot()
@@ -506,29 +576,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
return;
}
let jsonFile: Asset.File;
let modelFile: Asset.File;
if (file.name.toLowerCase().endsWith('.zip')) {
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
if (data['model.json']) {
modelFile = Asset.File(new File([data['model.json']], 'model.json'));
} else {
throw new Error('model.json missing from zip file');
}
if (data['results.bin']) {
resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
}
objectForEach(data, (v, k) => {
if (k === 'model.json') return;
if (k === 'results.bin') return;
ingredientFiles.push(Asset.File(new File([v], k)));
});
} else {
jsonFile = file;
modelFile = file;
}
cellPackJson = state.build().toRoot()
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
.apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
}
const cellPackBuilder = cellPackJson
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack);
.apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
const { packings } = cellPackObject.obj!.data;
const { packings } = cellPackObject.obj!.data;
await handleHivRna(plugin, packings, params.baseUrl);
for (let i = 0, il = packings.length; i < il; ++i) {
@@ -544,8 +621,30 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
representation: params.preset.representation,
};
await CellpackPackingPreset.apply(packing, packingParams, plugin);
if ( packings[i].location === 'surface' && params.membrane){
await loadMembrane(plugin, packings[i].name, state, params);
if (packings[i].compartment) {
if (params.membrane === 'lipids') {
if (packings[i].compartment!.geom_type) {
if (packings[i].compartment!.geom_type === 'file') {
// TODO: load mesh files or vertex,faces data
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
} else if (packings[i].compartment!.compartment_primitives) {
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
}
} else {
// try loading membrane from repo as a bcif file or from the given list of files.
if (params.membrane === 'lipids') {
await loadMembrane(plugin, packings[i].name, state, params);
}
}
} else if (params.membrane === 'geometry') {
if (packings[i].compartment!.compartment_primitives) {
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
} else if (packings[i].compartment!.geom_type === 'file') {
if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
}
}
}
}
}
}
@@ -555,19 +654,19 @@ const LoadCellPackModelParams = {
'id': PD.Select('InfluenzaModel2.json', [
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
['HIV_immature_model.json', 'HIV immature'],
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
['Blood_HIV.json', 'Blood HIV'],
['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
['influenza_model1.json', 'Influenza envelope'],
['InfluenzaModel2.json', 'Influenza Complete'],
['InfluenzaModel2.json', 'Influenza complete'],
['ExosomeModel.json', 'Exosome Model'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
}, { options: [['id', 'Id'], ['file', 'File']] }),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
membrane: PD.Boolean(true),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
@@ -581,4 +680,4 @@ export const LoadCellPackModel = StateAction.build({
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
await loadPackings(ctx, taskCtx, state, params);
}));
}));

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { StateObjectRef } from '../../mol-state';
@@ -9,8 +10,6 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '..
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ColorNames } from '../../mol-util/color/names';
import { CellPackGenerateColorThemeProvider } from './color/generate';
import { CellPackInfoProvider } from './property';
import { CellPackProvidedColorThemeProvider } from './color/provided';
export const CellpackPackingPresetParams = {
traceOnly: PD.Boolean(true),
@@ -42,8 +41,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
Object.assign(reprProps, { sizeFactor: 2 });
}
const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
// default is generated
const color = CellPackGenerateColorThemeProvider.name;
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
const representations = {
@@ -92,4 +91,4 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
return { components, representations };
}
});
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
};
}
});
});

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { Shape } from '../../mol-model/shape';
import { ColorNames } from '../../mol-util/color/names';
import { RuntimeContext } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
import { Sphere } from '../../mol-geo/primitive/sphere';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
interface MembraneSphereData {
radius: number
center: Vec3
}
const MembraneSphereParams = {
...Mesh.Params,
cellColor: PD.Color(ColorNames.orange),
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
center: PD.Vec3(Vec3.create(0, 0, 0)),
quality: { ...Mesh.Params.quality, isEssential: false },
};
type MeshParams = typeof MembraneSphereParams
const MembraneSphereVisuals = {
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
};
export const MBParams = {
...MembraneSphereParams
};
export type MBParams = typeof MBParams
export type UnitcellProps = PD.Values<MBParams>
function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
const state = MeshBuilder.createState(256, 128, mesh);
const radius = props.radius;
const asphere = Sphere(3);
const trans: Mat4 = Mat4.identity();
Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
state.currentGroup = 1;
MeshBuilder.addPrimitive(state, trans, asphere);
const m = MeshBuilder.getMesh(state);
return m;
}
function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
const geo = getMBMesh(data, props, shape && shape.geometry);
const label = 'mb';
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
}
export type MBRepresentation = Representation<MembraneSphereData, MBParams>
export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
}

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
@@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context';
import { CellPackInfoProvider } from './property';
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { Vec3, Quat } from '../../mol-math/linear-algebra';
import { StateTransformer } from '../../mol-state';
import { MBRepresentation, MBParams } from './representation';
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
import { getFloatValue } from './util';
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
export { ParseCellPack };
@@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({
name: 'parse-cellpack',
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
from: PSO.Format.Json,
to: CellPack
to: CellPack,
params: a => {
return {
resultsFile: PD.File({ accept: '.bin' }),
baseUrl: PD.Text(DefaultCellPackBaseUrl)
};
}
})({
apply({ a }) {
apply({ a, params, cache }, plugin: PluginContext) {
return Task.create('Parse CellPack', async ctx => {
const cell = a.data as Cell;
let counter_id = 0;
let fiber_counter_id = 0;
let comp_counter = 0;
const packings: CellPacking[] = [];
const { compartments, cytoplasme } = cell;
if (!cell.mapping_ids) cell.mapping_ids = {};
if (cytoplasme) {
packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
for (const iName in cytoplasme.ingredients) {
if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
if (compartments) {
for (const name in compartments) {
const { surface, interior } = compartments[name];
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
let filename = '';
if (compartments[name].geom_type === 'file') {
filename = (compartments[name].geom) ? compartments[name].geom as string : '';
}
const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
if (surface) {
packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
for (const iName in surface.ingredients) {
if (surface.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
if (interior) {
if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
for (const iName in interior.ingredients) {
if (interior.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
}
}
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
const { options } = cell;
let resultsAsset: Asset.Wrapper<'binary'> | undefined;
if (params.resultsFile) {
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
} else if (options?.resultfile) {
const url = `${params.baseUrl}/results/${options.resultfile}`;
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
}
if (resultsAsset) {
(cache as any).asset = resultsAsset;
const results = resultsAsset.data;
// flip the byte order if needed
const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
const numbers = new DataView(buffer);
const ninst = getFloatValue(numbers, 0);
const npoints = getFloatValue(numbers, 4);
const ncurve = getFloatValue(numbers, 8);
let offset = 12;
if (ninst !== 0) {
const pos = new Float32Array(buffer, offset, ninst * 4);
offset += ninst * 4 * 4;
const quat = new Float32Array(buffer, offset, ninst * 4);
offset += ninst * 4 * 4;
for (let i = 0; i < ninst; i++) {
const x: number = pos[i * 4 + 0];
const y: number = pos[i * 4 + 1];
const z: number = pos[i * 4 + 2];
const ingr_id = pos[i * 4 + 3] as number;
const pid = cell.mapping_ids![ingr_id];
if (!packings[pid[0]].ingredients[pid[1]].results) {
packings[pid[0]].ingredients[pid[1]].results = [];
}
packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
}
}
if (npoints !== 0) {
const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
offset += npoints * 4 * 4;
offset += npoints * 4 * 4;
const ctr_info = new Float32Array(buffer, offset, npoints * 4);
offset += npoints * 4 * 4;
const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
offset += ncurve * 4 * 4;
let counter = 0;
let ctr_points: Vec3[] = [];
let prev_ctype = 0;
let prev_cid = 0;
for (let i = 0; i < npoints; i++) {
const x: number = -ctr_pos[i * 4 + 0];
const y: number = ctr_pos[i * 4 + 1];
const z: number = ctr_pos[i * 4 + 2];
const cid: number = ctr_info[i * 4 + 0]; // curve id
const ctype: number = curve_ids[cid * 4 + 0]; // curve type
// cid 148 165 -1 0
// console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
if (prev_ctype !== ctype) {
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
ctr_points = [];
counter = 0;
} else if (prev_cid !== cid) {
ctr_points = [];
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
counter += 1;
}
ctr_points.push(Vec3.create(x, y, z));
prev_ctype = ctype;
prev_cid = cid;
}
// do the last one
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
}
}
return new CellPack({ cell, packings });
});
}
},
dispose({ cache }) {
((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
},
});
export { StructureFromCellpack };
@@ -77,14 +229,13 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
});
(cache as any).assets = assets;
return new PSO.Molecule.Structure(structure, { label: packing.name });
return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
});
},
dispose({ b, cache }) {
const assets = (cache as any).assets as Asset.Wrapper[];
if(assets) {
if (assets) {
for (const a of assets) a.dispose();
}
@@ -115,17 +266,17 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
// TODO: optimze
// TODO: think of ways how to fast-track changes to this for animations
const model = a.data;
let initial_structure = Structure.ofModel(model);
const initial_structure = Structure.ofModel(model);
const structures: Structure[] = [];
let structure: Structure = initial_structure;
// the list of asambly *?
const symmetry = ModelSymmetry.Provider.get(model);
if (symmetry && symmetry.assemblies.length !== 0){
if (symmetry && symmetry.assemblies.length !== 0) {
for (const a of symmetry.assemblies) {
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
structures.push(s);
}
const builder = Structure.Builder();
const builder = Structure.Builder({ label: 'Membrane' });
let offsetInvariantId = 0;
for (const s of structures) {
let maxInvariantId = 0;
@@ -137,7 +288,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
offsetInvariantId += maxInvariantId + 1;
}
structure = builder.getStructure();
for( let i = 0, il = structure.models.length; i < il; ++i) {
for (let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
}
@@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
b?.data.customPropertyDescriptors.dispose();
}
});
const CreateTransformer = StateTransformer.builderFactory('cellPACK');
export const CreateCompartmentSphere = CreateTransformer({
name: 'create-compartment-sphere',
display: 'CompartmentSphere',
from: PSO.Root, // or whatever data source
to: PSO.Shape.Representation3D,
params: {
center: PD.Vec3(Vec3()),
radius: PD.Numeric(1),
label: PD.Text(`Compartment Sphere`)
}
})({
canAutoUpdate({ oldParams, newParams }) {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Compartment Sphere', async ctx => {
const data = params;
const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
});
}
});

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { CIF } from '../../mol-io/reader/cif';
@@ -37,11 +38,11 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, asset
}
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
return { mmcif: cif.blocks[0], asset };
}
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager){
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, `https://opm-assets.storage.googleapis.com/pdb/${pdbId.toLowerCase()}.pdb`), 'string'));
return { pdb: await parsePDBfile(plugin, asset.data, pdbId), asset };
}
@@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) {
}
const { elementCount } = structure;
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
}
export function getFloatValue(value: DataView, offset: number) {
// if the last byte is a negative value (MSB is 1), the final
// float should be too
const negative = value.getInt8(offset + 2) >>> 31;
// this is how the bytes are arranged in the byte array/DataView
// buffer
const [b0, b1, b2, exponent] = [
// get first three bytes as unsigned since we only care
// about the last 8 bits of 32-bit js number returned by
// getUint8().
// Should be the same as: getInt8(offset) & -1 >>> 24
value.getUint8(offset),
value.getUint8(offset + 1),
value.getUint8(offset + 2),
// get the last byte, which is the exponent, as a signed int
// since it's already correct
value.getInt8(offset + 3)
];
let mantissa = b0 | (b1 << 8) | (b2 << 16);
if (negative) {
// need to set the most significant 8 bits to 1's since a js
// number is 32 bits but our mantissa is only 24.
mantissa |= 255 << 24;
}
return mantissa * Math.pow(10, exponent);
}

View File

@@ -41,10 +41,10 @@ export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider(
let pyramidsRepr;
if (representations)
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' } );
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' });
await update.commit({ revertOnError: true });
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
}
});

View File

@@ -27,7 +27,7 @@ const ColorMapping: ReadonlyMap<ConformerClasses, Color> = new Map([
['B', Color(0xC8CFFF)],
['BII', Color(0x0059DA)],
['miB', Color(0x3BE8FB)],
['Z', Color(0x01F60E)],
['Z', Color(0x01F60E)],
['IC', Color(0xFA5CFB)],
['OPN', Color(0xE90000)],
['SYN', Color(0xFFFF01)],
@@ -165,8 +165,8 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
legend: TableLegend(iterableToArray(ColorMapping.entries()).map(([conformer, color]) => {
return [conformer, color] as [string, Color];
}).concat([
[ 'Error', ErrorColor ],
[ 'Unknown', DefaultColor ]
['Error', ErrorColor],
['Unknown', DefaultColor]
]))
};
}
@@ -181,6 +181,6 @@ export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramid
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ConfalPyramidsProvider.descriptor, false)
detach: (data) => data.structure && ConfalPyramidsProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -20,10 +20,10 @@ import { Structure, StructureProperties, Unit } from '../../../mol-model/structu
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
import { StructureGroup, UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { VisualContext } from '../../../mol-repr/visual';
import { getAltResidueLociFromId } from '../../../mol-repr/structure/visual/util/common';
import { getAltResidueLociFromId, StructureGroup } from '../../../mol-repr/structure/visual/util/common';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
import { NullLocation } from '../../../mol-model/location';

View File

@@ -95,7 +95,7 @@ export namespace ConfalPyramidsUtil {
const first = residueInfoFromLocation(locFirst);
const second = residueInfoFromLocation(locSecond);
const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';

View File

@@ -61,7 +61,7 @@ function getColumns(block: G3dDataBlock) {
objectForEach(data, (hs, h) => {
objectForEach(hs, (chs, ch) => {
const entity_id = `${ch}-${h}`;
const l = chs.start.length;
const l = chs.start.length;
if (l === 0) return;
let x = chs.x[0];

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { getStyle } from '../../mol-gl/renderer';
import { Box3D } from '../../mol-math/geometry';
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateSelection } from '../../mol-state';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { SetUtils } from '../../mol-util/set';
import { GlbExporter } from './glb-exporter';
import { ObjExporter } from './obj-exporter';
import { StlExporter } from './stl-exporter';
import { UsdzExporter } from './usdz-exporter';
export const GeometryParams = {
format: PD.Select('glb', [
['glb', 'glTF 2.0 Binary (.glb)'],
['stl', 'Stl (.stl)'],
['obj', 'Wavefront (.obj)'],
['usdz', 'Universal Scene Description (.usdz)']
])
};
export class GeometryControls extends PluginComponent {
readonly behaviors = {
params: this.ev.behavior<PD.Values<typeof GeometryParams>>(PD.getDefaultValues(GeometryParams))
}
private getFilename() {
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
const uniqueIds = new Set<string>();
models.forEach(m => uniqueIds.add(m.entryId.toUpperCase()));
const idString = SetUtils.toArray(uniqueIds).join('-');
return `${idString || 'molstar-model'}`;
}
exportGeometry() {
const task = Task.create('Export Geometry', async ctx => {
try {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
switch (this.behaviors.params.value.format) {
case 'glb':
renderObjectExporter = new GlbExporter(style, boundingBox);
break;
case 'obj':
renderObjectExporter = new ObjExporter(filename, boundingBox);
break;
case 'stl':
renderObjectExporter = new StlExporter(boundingBox);
break;
case 'usdz':
renderObjectExporter = new UsdzExporter(style, boundingBox, boundingSphere.radius);
break;
default: throw new Error('Unsupported format.');
}
for (let i = 0, il = renderObjects.length; i < il; ++i) {
await ctx.update({ message: `Exporting object ${i}/${il}` });
await renderObjectExporter.add(renderObjects[i], this.plugin.canvas3d?.webgl!, ctx);
}
const blob = await renderObjectExporter.getBlob(ctx);
return {
blob,
filename: filename + '.' + renderObjectExporter.fileExtension
};
} catch (e) {
this.plugin.log.error('Error during geometry export');
throw e;
}
});
return this.plugin.runTask(task, { useOverlay: true });
}
constructor(private plugin: PluginContext) {
super();
}
}

View File

@@ -0,0 +1,327 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { PLUGIN_VERSION } from '../../mol-plugin/version';
import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color/color';
import { fillSerial } from '../../mol-util/array';
import { NumberArray } from '../../mol-util/type-helpers';
import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3normalize = Vec3.normalize;
const v3toArray = Vec3.toArray;
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
const UNSIGNED_BYTE = 5121;
const UNSIGNED_INT = 5125;
const FLOAT = 5126;
const ARRAY_BUFFER = 34962;
const ELEMENT_ARRAY_BUFFER = 34963;
const GLTF_MAGIC_BYTE = 0x46546C67;
const JSON_CHUNK_TYPE = 0x4E4F534A;
const BIN_CHUNK_TYPE = 0x004E4942;
const JSON_PAD_CHAR = 0x20;
const BIN_PAD_CHAR = 0x00;
export type GlbData = {
glb: Uint8Array
}
export class GlbExporter extends MeshExporter<GlbData> {
readonly fileExtension = 'glb';
private nodes: Record<string, any>[] = [];
private meshes: Record<string, any>[] = [];
private accessors: Record<string, any>[] = [];
private bufferViews: Record<string, any>[] = [];
private binaryBuffer: ArrayBuffer[] = [];
private byteOffset = 0;
private centerTransform: Mat4;
private static vec3MinMax(a: NumberArray) {
const min: number[] = [Infinity, Infinity, Infinity];
const max: number[] = [-Infinity, -Infinity, -Infinity];
for (let i = 0, il = a.length; i < il; i += 3) {
for (let j = 0; j < 3; ++j) {
min[j] = Math.min(a[i + j], min[j]);
max[j] = Math.max(a[i + j], max[j]);
}
}
return [min, max];
}
private addBuffer(buffer: ArrayBuffer, componentType: number, type: string, count: number, target: number, min?: any, max?: any, normalized?: boolean) {
this.binaryBuffer.push(buffer);
const bufferViewOffset = this.bufferViews.length;
this.bufferViews.push({
buffer: 0,
byteOffset: this.byteOffset,
byteLength: buffer.byteLength,
target
});
this.byteOffset += buffer.byteLength;
const accessorOffset = this.accessors.length;
this.accessors.push({
bufferView: bufferViewOffset,
byteOffset: 0,
componentType,
count,
type,
min,
max,
normalized
});
return accessorOffset;
}
private addGeometryBuffers(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, vertexCount: number, drawCount: number, isGeoTexture: boolean) {
const tmpV = Vec3();
const stride = isGeoTexture ? 4 : 3;
const vertexArray = new Float32Array(vertexCount * 3);
const normalArray = new Float32Array(vertexCount * 3);
let indexArray: Uint32Array | undefined;
// position
for (let i = 0; i < vertexCount; ++i) {
v3fromArray(tmpV, vertices, i * stride);
v3toArray(tmpV, vertexArray, i * 3);
}
// normal
for (let i = 0; i < vertexCount; ++i) {
v3fromArray(tmpV, normals, i * stride);
v3normalize(tmpV, tmpV);
v3toArray(tmpV, normalArray, i * 3);
}
// face
if (!isGeoTexture) {
indexArray = indices!.slice(0, drawCount);
}
const [vertexMin, vertexMax] = GlbExporter.vec3MinMax(vertexArray);
let vertexBuffer = vertexArray.buffer;
let normalBuffer = normalArray.buffer;
let indexBuffer = isGeoTexture ? undefined : indexArray!.buffer;
if (!IsNativeEndianLittle) {
vertexBuffer = flipByteOrder(new Uint8Array(vertexBuffer), 4);
normalBuffer = flipByteOrder(new Uint8Array(normalBuffer), 4);
if (!isGeoTexture) indexBuffer = flipByteOrder(new Uint8Array(indexBuffer!), 4);
}
return {
vertexAccessorIndex: this.addBuffer(vertexBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER, vertexMin, vertexMax),
normalAccessorIndex: this.addBuffer(normalBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER),
indexAccessorIndex: isGeoTexture ? undefined : this.addBuffer(indexBuffer!, UNSIGNED_INT, 'SCALAR', drawCount, ELEMENT_ARRAY_BUFFER)
};
}
private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) {
const { values, vertexCount } = geoData;
const uAlpha = values.uAlpha.ref.value;
const colorArray = new Uint8Array(vertexCount * 4);
for (let i = 0; i < vertexCount; ++i) {
let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint);
const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = uAlpha * (1 - transparency);
color = Color.sRGBToLinear(color);
Color.toArray(color, colorArray, i * 4);
colorArray[i * 4 + 3] = Math.round(alpha * 255);
}
let colorBuffer = colorArray.buffer;
if (!IsNativeEndianLittle) {
colorBuffer = flipByteOrder(new Uint8Array(colorBuffer), 4);
}
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
const t = Mat4();
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const dTransparency = values.dTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint: Uint8Array | undefined;
if (overpaintType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency: Uint8Array | undefined;
if (transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
}
// instancing
const sameGeometryBuffers = mesh !== undefined;
const sameColorBuffer = sameGeometryBuffers && colorType !== 'instance' && !colorType.endsWith('Instance') && !dTransparency;
let vertexAccessorIndex: number;
let normalAccessorIndex: number;
let indexAccessorIndex: number | undefined;
let colorAccessorIndex: number;
let meshIndex: number;
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
// create a glTF mesh if needed
if (instanceIndex === 0 || !sameGeometryBuffers || !sameColorBuffer) {
const { vertices, normals, indices, groups, vertexCount, drawCount } = GlbExporter.getInstance(input, instanceIndex);
// create geometry buffers if needed
if (instanceIndex === 0 || !sameGeometryBuffers) {
const accessorIndices = this.addGeometryBuffers(vertices, normals, indices, vertexCount, drawCount, isGeoTexture);
vertexAccessorIndex = accessorIndices.vertexAccessorIndex;
normalAccessorIndex = accessorIndices.normalAccessorIndex;
indexAccessorIndex = accessorIndices.indexAccessorIndex;
}
// create a color buffer if needed
if (instanceIndex === 0 || !sameColorBuffer) {
colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
}
// glTF mesh
meshIndex = this.meshes.length;
this.meshes.push({
primitives: [{
attributes: {
POSITION: vertexAccessorIndex!,
NORMAL: normalAccessorIndex!,
COLOR_0: colorAccessorIndex!
},
indices: indexAccessorIndex,
material: 0
}]
});
}
// node
Mat4.fromArray(t, aTransform, instanceIndex * 16);
Mat4.mul(t, this.centerTransform, t);
const node: Record<string, any> = {
mesh: meshIndex!,
matrix: t.slice()
};
this.nodes.push(node);
}
}
async getData() {
const binaryBufferLength = this.byteOffset;
const gltf = {
asset: {
version: '2.0',
generator: `Mol* ${PLUGIN_VERSION}`
},
scenes: [{
nodes: fillSerial(new Array(this.nodes.length) as number[])
}],
nodes: this.nodes,
meshes: this.meshes,
buffers: [{
byteLength: binaryBufferLength,
}],
bufferViews: this.bufferViews,
accessors: this.accessors,
materials: [{
pbrMetallicRoughness: {
baseColorFactor: [1, 1, 1, 1],
metallicFactor: this.style.metalness,
roughnessFactor: this.style.roughness
}
}]
};
const createChunk = (chunkType: number, data: ArrayBuffer[], byteLength: number, padChar: number): [ArrayBuffer[], number] => {
let padding = null;
if (byteLength % 4 !== 0) {
const pad = 4 - (byteLength % 4);
byteLength += pad;
padding = new Uint8Array(pad);
padding.fill(padChar);
}
const preamble = new ArrayBuffer(8);
const preambleDataView = new DataView(preamble);
preambleDataView.setUint32(0, byteLength, true);
preambleDataView.setUint32(4, chunkType, true);
const chunk = [preamble, ...data];
if (padding) {
chunk.push(padding.buffer);
}
return [chunk, 8 + byteLength];
};
const jsonString = JSON.stringify(gltf);
const jsonBuffer = new Uint8Array(jsonString.length);
asciiWrite(jsonBuffer, jsonString);
const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR);
const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR);
const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
const header = new ArrayBuffer(12);
const headerDataView = new DataView(header);
headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF"
headerDataView.setUint32(4, 2, true); // version
headerDataView.setUint32(8, glbBufferLength, true); // length
const glbBuffer = [header, ...jsonChunk, ...binaryChunk];
const glb = new Uint8Array(glbBufferLength);
let offset = 0;
for (const buffer of glbBuffer) {
glb.set(new Uint8Array(buffer), offset);
offset += buffer.byteLength;
}
return { glb };
}
async getBlob(ctx: RuntimeContext) {
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
}
constructor(private style: Style, boundingBox: Box3D) {
super();
const tmpV = Vec3();
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
Vec3.scale(tmpV, tmpV, -0.5);
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { GeometryExporterUI } from './ui';
export const GeometryExport = PluginBehavior.create<{ }>({
name: 'extension-geo-export',
category: 'misc',
display: {
name: 'Geometry Export'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.customStructureControls.set('geo-export', GeometryExporterUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('geo-export');
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,485 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { sort, arraySwap } from '../../mol-data/util';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { MeshValues } from '../../mol-gl/renderable/mesh';
import { LinesValues } from '../../mol-gl/renderable/lines';
import { PointsValues } from '../../mol-gl/renderable/points';
import { SpheresValues } from '../../mol-gl/renderable/spheres';
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
import { TextureMeshValues } from '../../mol-gl/renderable/texture-mesh';
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
import { TextureImage } from '../../mol-gl/renderable/util';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { getTrilinearlyInterpolated } from '../../mol-geo/geometry/mesh/color-smoothing';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
import { Vec3 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
const GeoExportName = 'geo-export';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
export interface AddMeshInput {
mesh: {
vertices: Float32Array
normals: Float32Array
indices: Uint32Array | undefined
groups: Float32Array | Uint8Array
vertexCount: number
drawCount: number
} | undefined
meshes: Mesh[] | undefined
values: BaseValues
isGeoTexture: boolean
webgl: WebGLContext | undefined
ctx: RuntimeContext
}
export type MeshGeoData = {
values: BaseValues,
groups: Float32Array | Uint8Array,
vertexCount: number,
instanceIndex: number,
isGeoTexture: boolean
}
export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
abstract readonly fileExtension: string;
private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
const r = tSize.array[i * 3];
const g = tSize.array[i * 3 + 1];
const b = tSize.array[i * 3 + 2];
return decodeFloatRGB(r, g, b) / sizeDataFactor;
}
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
const tSize = values.tSize.ref.value;
let size = 0;
switch (values.dSizeType.ref.value) {
case 'uniform':
size = values.uSize.ref.value;
break;
case 'instance':
size = MeshExporter.getSizeFromTexture(tSize, instanceIndex);
break;
case 'group':
size = MeshExporter.getSizeFromTexture(tSize, group);
break;
case 'groupInstance':
const groupCount = values.uGroupCount.ref.value;
size = MeshExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group);
break;
}
return size * values.uSizeFactor.ref.value;
}
protected static getGroup(groups: Float32Array | Uint8Array, i: number): number {
const i4 = i * 4;
const r = groups[i4];
const g = groups[i4 + 1];
const b = groups[i4 + 2];
if (groups instanceof Float32Array) {
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
}
return decodeFloatRGB(r, g, b);
}
protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
const { values, vertexCount, vertices, colorType, stride } = input;
const colorGridTransform = values.uColorGridTransform.ref.value;
const colorGridDim = values.uColorGridDim.ref.value;
const colorTexDim = values.uColorTexDim.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
return interpolated.array;
}
protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
const { values, vertexCount, vertices, colorType, stride } = input;
const overpaintGridTransform = values.uOverpaintGridTransform.ref.value;
const overpaintGridDim = values.uOverpaintGridDim.ref.value;
const overpaintTexDim = values.uOverpaintTexDim.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array;
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 });
return interpolated.array;
}
protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
const { values, vertexCount, vertices, colorType, stride } = input;
const transparencyGridTransform = values.uTransparencyGridTransform.ref.value;
const transparencyGridDim = values.uTransparencyGridDim.ref.value;
const transparencyTexDim = values.uTransparencyTexDim.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array;
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 });
return interpolated.array;
}
protected static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
if (vertexCount <= 1024) return;
const rgb = Vec3();
const min = Vec3();
const max = Vec3();
const sum = Vec3();
const colorMap = new Map<Color, Color>();
const colorComparers = [
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
];
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
if (l > r) return;
if (l === r || depth >= 10) {
// Find the average color.
Vec3.set(sum, 0, 0, 0);
for (let i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
Vec3.add(sum, sum, rgb);
}
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
const averageColor = Color.fromArray(rgb, 0);
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
return;
}
// Find the color channel with the greatest range.
Vec3.set(min, 255, 255, 255);
Vec3.set(max, 0, 0, 0);
for (let i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
for (let j = 0; j < 3; ++j) {
Vec3.min(min, min, rgb);
Vec3.max(max, max, rgb);
}
}
let k = 0;
if (max[1] - min[1] > max[k] - min[k]) k = 1;
if (max[2] - min[2] > max[k] - min[k]) k = 2;
sort(colors, l, r + 1, colorComparers[k], arraySwap);
const m = (l + r) >> 1;
medianCut(colors, l, m, depth + 1);
medianCut(colors, m + 1, r, depth + 1);
};
// Create an array of unique colors and use the median cut algorithm.
const colorSet = new Set<Color>();
for (let i = 0; i < vertexCount; ++i) {
colorSet.add(Color.fromArray(colorArray, i * 3));
}
const colors = Array.from(colorSet);
medianCut(colors, 0, colors.length - 1, 0);
// Map actual colors to quantized colors.
for (let i = 0; i < vertexCount; ++i) {
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
Color.toArray(color!, colorArray, i * 3);
}
}
protected static getInstance(input: AddMeshInput, instanceIndex: number) {
const { mesh, meshes } = input;
if (mesh !== undefined) {
return mesh;
} else {
const mesh = meshes![instanceIndex];
return {
vertices: mesh.vertexBuffer.ref.value,
normals: mesh.normalBuffer.ref.value,
indices: mesh.indexBuffer.ref.value,
groups: mesh.groupBuffer.ref.value,
vertexCount: mesh.vertexCount,
drawCount: mesh.triangleCount * 3
};
}
}
protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const uColor = values.uColor.ref.value;
const tColor = values.tColor.ref.value.array;
const overpaintType = values.dOverpaintType.ref.value;
const dOverpaint = values.dOverpaint.ref.value;
const tOverpaint = values.tOverpaint.ref.value.array;
let color: Color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(uColor, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
}
case 'vertex':
color = Color.fromArray(tColor, vertexIndex * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * vertexCount + vertexIndex) * 3);
break;
case 'volume':
color = Color.fromArray(interpolatedColors!, vertexIndex * 3);
break;
case 'volumeInstance':
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + vertexIndex) * 3);
break;
default: throw new Error('Unsupported color type.');
}
if (dOverpaint) {
let overpaintColor: Color;
let overpaintAlpha: number;
switch (overpaintType) {
case 'groupInstance': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
const idx = (instanceIndex * groupCount + group) * 4;
overpaintColor = Color.fromArray(tOverpaint, idx);
overpaintAlpha = tOverpaint[idx + 3] / 255;
break;
}
case 'vertexInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
overpaintColor = Color.fromArray(tOverpaint, idx);
overpaintAlpha = tOverpaint[idx + 3] / 255;
break;
}
case 'volumeInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
overpaintColor = Color.fromArray(interpolatedOverpaint!, idx);
overpaintAlpha = interpolatedOverpaint![idx + 3] / 255;
break;
}
default: throw new Error('Unsupported overpaint type.');
}
// interpolate twice to avoid darkening due to empty overpaint
overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha);
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
}
return color;
}
protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
const groupCount = values.uGroupCount.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value.array;
const transparencyType = values.dTransparencyType.ref.value;
let transparency: number = 0;
if (dTransparency) {
switch (transparencyType) {
case 'groupInstance': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
const idx = (instanceIndex * groupCount + group);
transparency = tTransparency[idx] / 255;
break;
}
case 'vertexInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex);
transparency = tTransparency[idx] / 255;
break;
}
case 'volumeInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex);
transparency = interpolatedTransparency![idx] / 255;
break;
}
default: throw new Error('Unsupported transparency type.');
}
}
return transparency;
}
protected abstract addMeshWithColors(input: AddMeshInput): void;
private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
const aPosition = values.aPosition.ref.value;
const aNormal = values.aNormal.ref.value;
const aGroup = values.aGroup.ref.value;
const originalData = Mesh.getOriginalData(values);
let indices: Uint32Array;
let vertexCount: number;
let drawCount: number;
if (originalData) {
indices = originalData.indexBuffer;
vertexCount = originalData.vertexCount;
drawCount = originalData.triangleCount * 3;
} else {
indices = values.elements.ref.value;
vertexCount = values.uVertexCount.ref.value;
drawCount = values.drawCount.ref.value;
}
await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, webgl, ctx });
}
private async addLines(values: LinesValues, webgl: WebGLContext, ctx: RuntimeContext) {
// TODO
}
private async addPoints(values: PointsValues, webgl: WebGLContext, ctx: RuntimeContext) {
// TODO
}
private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
const center = Vec3();
const aPosition = values.aPosition.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
const meshes: Mesh[] = [];
const sphereCount = vertexCount / 4 * instanceCount;
let detail: number;
if (sphereCount < 2000) detail = 3;
else if (sphereCount < 20000) detail = 2;
else detail = 1;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 4) {
v3fromArray(center, aPosition, i * 3);
const group = aGroup[i];
const radius = MeshExporter.getSize(values, instanceIndex, group);
state.currentGroup = group;
addSphere(state, center, radius, detail);
}
meshes.push(MeshBuilder.getMesh(state));
}
await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
}
private async addCylinders(values: CylindersValues, webgl: WebGLContext, ctx: RuntimeContext) {
const start = Vec3();
const end = Vec3();
const aStart = values.aStart.ref.value;
const aEnd = values.aEnd.ref.value;
const aScale = values.aScale.ref.value;
const aCap = values.aCap.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
const meshes: Mesh[] = [];
const cylinderCount = vertexCount / 6 * instanceCount;
let radialSegments: number;
if (cylinderCount < 2000) radialSegments = 36;
else if (cylinderCount < 20000) radialSegments = 24;
else radialSegments = 12;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 6) {
v3fromArray(start, aStart, i * 3);
v3fromArray(end, aEnd, i * 3);
const group = aGroup[i];
const radius = MeshExporter.getSize(values, instanceIndex, group) * aScale[i];
const cap = aCap[i];
const topCap = cap === 1 || cap === 3;
const bottomCap = cap >= 2;
const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments };
state.currentGroup = aGroup[i];
addCylinder(state, start, end, 1, cylinderProps);
}
meshes.push(MeshBuilder.getMesh(state));
}
await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
}
private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[GeoExportName];
const [width, height] = values.uGeoTexDim.ref.value;
const vertices = new Float32Array(width * height * 4);
const normals = new Float32Array(width * height * 4);
const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4);
framebuffer.bind();
values.tPosition.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, vertices);
values.tNormal.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, normals);
values.tGroup.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, groups);
const vertexCount = values.uVertexCount.ref.value;
const drawCount = values.drawCount.ref.value;
await this.addMeshWithColors({ mesh: { vertices, normals, indices: undefined, groups, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: true, webgl, ctx });
}
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
if (!renderObject.state.visible) return;
switch (renderObject.type) {
case 'mesh':
return this.addMesh(renderObject.values as MeshValues, webgl, ctx);
case 'lines':
return this.addLines(renderObject.values as LinesValues, webgl, ctx);
case 'points':
return this.addPoints(renderObject.values as PointsValues, webgl, ctx);
case 'spheres':
return this.addSpheres(renderObject.values as SpheresValues, webgl, ctx);
case 'cylinders':
return this.addCylinders(renderObject.values as CylindersValues, webgl, ctx);
case 'texture-mesh':
return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
}
}
abstract getData(ctx: RuntimeContext): Promise<D>;
abstract getBlob(ctx: RuntimeContext): Promise<Blob>;
}

View File

@@ -0,0 +1,207 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { asciiWrite } from '../../mol-io/common/ascii';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { StringBuilder } from '../../mol-util';
import { Color } from '../../mol-util/color/color';
import { zip } from '../../mol-util/zip/zip';
import { MeshExporter, AddMeshInput } from './mesh-exporter';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4 = Vec3.transformMat4;
const v3transformMat3 = Vec3.transformMat3;
const mat3directionTransform = Mat3.directionTransform;
// http://paulbourke.net/dataformats/obj/
// http://paulbourke.net/dataformats/mtl/
export type ObjData = {
obj: string
mtl: string
}
export class ObjExporter extends MeshExporter<ObjData> {
readonly fileExtension = 'zip';
private obj = StringBuilder.create();
private mtl = StringBuilder.create();
private vertexOffset = 0;
private currentColor: Color | undefined;
private currentAlpha: number | undefined;
private materialSet = new Set<string>();
private centerTransform: Mat4;
private updateMaterial(color: Color, alpha: number) {
if (this.currentColor === color && this.currentAlpha === alpha) return;
this.currentColor = color;
this.currentAlpha = alpha;
const material = Color.toHexString(color) + alpha;
StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
StringBuilder.newline(this.obj);
if (!this.materialSet.has(material)) {
this.materialSet.add(material);
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
const mtl = this.mtl;
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
StringBuilder.writeFloat(mtl, r, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, g, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, b, 1000);
StringBuilder.newline(mtl);
StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
StringBuilder.writeSafe(mtl, 'd '); // dissolve
StringBuilder.writeFloat(mtl, alpha, 1000);
StringBuilder.newline(mtl);
}
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
const obj = this.obj;
const t = Mat4();
const n = Mat3();
const tmpV = Vec3();
const stride = isGeoTexture ? 4 : 3;
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint: Uint8Array | undefined;
if (overpaintType === 'volumeInstance') {
interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency: Uint8Array | undefined;
if (transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
}
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
const { vertices, normals, indices, groups, vertexCount, drawCount } = ObjExporter.getInstance(input, instanceIndex);
Mat4.fromArray(t, aTransform, instanceIndex * 16);
Mat4.mul(t, this.centerTransform, t);
mat3directionTransform(n, t);
// position
for (let i = 0; i < vertexCount; ++i) {
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
StringBuilder.writeSafe(obj, 'v ');
StringBuilder.writeFloat(obj, tmpV[0], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 1000);
StringBuilder.newline(obj);
}
// normal
for (let i = 0; i < vertexCount; ++i) {
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
StringBuilder.writeSafe(obj, 'vn ');
StringBuilder.writeFloat(obj, tmpV[0], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 100);
StringBuilder.newline(obj);
}
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
// color
const quantizedColors = new Uint8Array(drawCount * 3);
for (let i = 0; i < drawCount; i += 3) {
const v = isGeoTexture ? i : indices![i];
const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
// face
for (let i = 0; i < drawCount; i += 3) {
const color = Color.fromArray(quantizedColors, i);
const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
this.updateMaterial(color, alpha);
const v1 = this.vertexOffset + (isGeoTexture ? i : indices![i]) + 1;
const v2 = this.vertexOffset + (isGeoTexture ? i + 1 : indices![i + 1]) + 1;
const v3 = this.vertexOffset + (isGeoTexture ? i + 2 : indices![i + 2]) + 1;
StringBuilder.writeSafe(obj, 'f ');
StringBuilder.writeInteger(obj, v1);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v1);
StringBuilder.writeInteger(obj, v2);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v2);
StringBuilder.writeInteger(obj, v3);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeInteger(obj, v3);
StringBuilder.newline(obj);
}
this.vertexOffset += vertexCount;
}
}
async getData() {
return {
obj: StringBuilder.getString(this.obj),
mtl: StringBuilder.getString(this.mtl)
};
}
async getBlob(ctx: RuntimeContext) {
const { obj, mtl } = await this.getData();
const objData = new Uint8Array(obj.length);
asciiWrite(objData, obj);
const mtlData = new Uint8Array(mtl.length);
asciiWrite(mtlData, mtl);
const zipDataObj = {
[this.filename + '.obj']: objData,
[this.filename + '.mtl']: mtlData
};
return new Blob([await zip(ctx, zipDataObj)], { type: 'application/zip' });
}
constructor(private filename: string, boundingBox: Box3D) {
super();
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
const tmpV = Vec3();
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
Vec3.scale(tmpV, tmpV, -0.5);
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
}
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { RuntimeContext } from '../../mol-task';
export type RenderObjectExportData = {
[k: string]: string | Uint8Array | ArrayBuffer | undefined
}
export interface RenderObjectExporter<D extends RenderObjectExportData> {
readonly fileExtension: string
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
getData(ctx: RuntimeContext): Promise<D>
getBlob(ctx: RuntimeContext): Promise<Blob>
}

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { asciiWrite } from '../../mol-io/common/ascii';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { PLUGIN_VERSION } from '../../mol-plugin/version';
import { RuntimeContext } from '../../mol-task';
import { MeshExporter, AddMeshInput } from './mesh-exporter';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4 = Vec3.transformMat4;
const v3triangleNormal = Vec3.triangleNormal;
const v3toArray = Vec3.toArray;
// https://www.fabbers.com/tech/STL_Format
export type StlData = {
stl: Uint8Array
}
export class StlExporter extends MeshExporter<StlData> {
readonly fileExtension = 'stl';
private triangleBuffers: ArrayBuffer[] = [];
private triangleCount = 0;
private centerTransform: Mat4;
protected async addMeshWithColors(input: AddMeshInput) {
const { values, isGeoTexture, ctx } = input;
const t = Mat4();
const tmpV = Vec3();
const v1 = Vec3();
const v2 = Vec3();
const v3 = Vec3();
const stride = isGeoTexture ? 4 : 3;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
const { vertices, indices, vertexCount, drawCount } = StlExporter.getInstance(input, instanceIndex);
Mat4.fromArray(t, aTransform, instanceIndex * 16);
Mat4.mul(t, this.centerTransform, t);
// position
const vertexArray = new Float32Array(vertexCount * 3);
for (let i = 0; i < vertexCount; ++i) {
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
v3toArray(tmpV, vertexArray, i * 3);
}
// face
const triangleBuffer = new ArrayBuffer(50 * drawCount);
const dataView = new DataView(triangleBuffer);
for (let i = 0; i < drawCount; i += 3) {
v3fromArray(v1, vertexArray, (isGeoTexture ? i : indices![i]) * 3);
v3fromArray(v2, vertexArray, (isGeoTexture ? i + 1 : indices![i + 1]) * 3);
v3fromArray(v3, vertexArray, (isGeoTexture ? i + 2 : indices![i + 2]) * 3);
v3triangleNormal(tmpV, v1, v2, v3);
const byteOffset = 50 * i;
dataView.setFloat32(byteOffset, tmpV[0], true);
dataView.setFloat32(byteOffset + 4, tmpV[1], true);
dataView.setFloat32(byteOffset + 8, tmpV[2], true);
dataView.setFloat32(byteOffset + 12, v1[0], true);
dataView.setFloat32(byteOffset + 16, v1[1], true);
dataView.setFloat32(byteOffset + 20, v1[2], true);
dataView.setFloat32(byteOffset + 24, v2[0], true);
dataView.setFloat32(byteOffset + 28, v2[1], true);
dataView.setFloat32(byteOffset + 32, v2[2], true);
dataView.setFloat32(byteOffset + 36, v3[0], true);
dataView.setFloat32(byteOffset + 40, v3[1], true);
dataView.setFloat32(byteOffset + 44, v3[2], true);
}
this.triangleBuffers.push(triangleBuffer);
this.triangleCount += drawCount;
}
}
async getData() {
const stl = new Uint8Array(84 + 50 * this.triangleCount);
asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`);
const dataView = new DataView(stl.buffer);
dataView.setUint32(80, this.triangleCount, true);
let byteOffset = 84;
for (const buffer of this.triangleBuffers) {
stl.set(new Uint8Array(buffer), byteOffset);
byteOffset += buffer.byteLength;
}
return { stl };
}
async getBlob(ctx: RuntimeContext) {
return new Blob([(await this.getData()).stl], { type: 'model/stl' });
}
constructor(boundingBox: Box3D) {
super();
const tmpV = Vec3();
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
Vec3.scale(tmpV, tmpV, -0.5);
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
}
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { merge } from 'rxjs';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button } from '../../mol-plugin-ui/controls/common';
import { GetAppSvg, CubeScanSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { download } from '../../mol-util/download';
import { GeometryParams, GeometryControls } from './controls';
interface State {
busy?: boolean
}
export class GeometryExporterUI extends CollapsableControls<{}, State> {
private _controls: GeometryControls | undefined;
private isARSupported: boolean | undefined;
get controls() {
return this._controls || (this._controls = new GeometryControls(this.plugin));
}
protected defaultState(): State & CollapsableState {
return {
header: 'Export Geometry',
isCollapsed: true,
brand: { accent: 'cyan', svg: CubeSendSvg }
};
}
protected renderControls(): JSX.Element {
if (this.isARSupported === undefined) {
this.isARSupported = !!document.createElement('a').relList?.supports?.('ar');
}
const ctrl = this.controls;
return <>
<ParameterControls
params={GeometryParams}
values={ctrl.behaviors.params.value}
onChangeValues={xs => ctrl.behaviors.params.next(xs)}
isDisabled={this.state.busy}
/>
<Button icon={GetAppSvg}
onClick={this.save} style={{ marginTop: 1 }}
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
Save
</Button>
{this.isARSupported && ctrl.behaviors.params.value.format === 'usdz' &&
<Button icon={CubeScanSvg}
onClick={this.viewInAR} style={{ marginTop: 1 }}
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
View in AR
</Button>
}
</>;
}
componentDidMount() {
const merged = merge(
this.controls.behaviors.params,
this.plugin.canvas3d!.reprCount
);
this.subscribe(merged, () => {
if (!this.state.isCollapsed) this.forceUpdate();
});
}
componentWillUnmount() {
this._controls?.dispose();
this._controls = void 0;
}
save = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
download(data.blob, data.filename);
} catch (e) {
console.error(e);
} finally {
this.setState({ busy: false });
}
}
viewInAR = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
const a = document.createElement('a');
a.rel = 'ar';
a.href = URL.createObjectURL(data.blob);
// For in-place viewing of USDZ on iOS, the link must contain a single child that is either an img or picture.
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/
a.appendChild(document.createElement('img'));
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
} catch (e) {
console.error(e);
} finally {
this.setState({ busy: false });
}
}
}

View File

@@ -0,0 +1,248 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
import { PLUGIN_VERSION } from '../../mol-plugin/version';
import { RuntimeContext } from '../../mol-task';
import { StringBuilder } from '../../mol-util';
import { Color } from '../../mol-util/color/color';
import { zip } from '../../mol-util/zip/zip';
import { MeshExporter, AddMeshInput } from './mesh-exporter';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4 = Vec3.transformMat4;
const v3transformMat3 = Vec3.transformMat3;
const mat3directionTransform = Mat3.directionTransform;
// https://graphics.pixar.com/usd/docs/index.html
export type UsdzData = {
usdz: ArrayBuffer
}
export class UsdzExporter extends MeshExporter<UsdzData> {
readonly fileExtension = 'usdz';
private meshes: string[] = [];
private materials: string[] = [];
private materialSet = new Set<number>();
private centerTransform: Mat4;
private static getMaterialKey(color: Color, alpha: number) {
return color * 256 + Math.round(alpha * 255);
}
private addMaterial(color: Color, alpha: number) {
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
if (this.materialSet.has(materialKey)) return;
this.materialSet.add(materialKey);
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
this.materials.push(`
def Material "material${materialKey}"
{
token outputs:surface.connect = </material${materialKey}/shader.outputs:surface>
def Shader "shader"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (${r},${g},${b})
float inputs:opacity = ${alpha}
float inputs:metallic = ${this.style.metalness}
float inputs:roughness = ${this.style.roughness}
token outputs:surface
}
}
`);
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
const t = Mat4();
const n = Mat3();
const tmpV = Vec3();
const stride = isGeoTexture ? 4 : 3;
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint: Uint8Array | undefined;
if (overpaintType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency: Uint8Array | undefined;
if (transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
}
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
const { vertices, normals, indices, groups, vertexCount, drawCount } = UsdzExporter.getInstance(input, instanceIndex);
Mat4.fromArray(t, aTransform, instanceIndex * 16);
Mat4.mul(t, this.centerTransform, t);
mat3directionTransform(n, t);
const vertexBuilder = StringBuilder.create();
const normalBuilder = StringBuilder.create();
const indexBuilder = StringBuilder.create();
// position
for (let i = 0; i < vertexCount; ++i) {
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
StringBuilder.writeSafe(vertexBuilder, (i === 0) ? '(' : ',(');
StringBuilder.writeFloat(vertexBuilder, tmpV[0], 10000);
StringBuilder.writeSafe(vertexBuilder, ',');
StringBuilder.writeFloat(vertexBuilder, tmpV[1], 10000);
StringBuilder.writeSafe(vertexBuilder, ',');
StringBuilder.writeFloat(vertexBuilder, tmpV[2], 10000);
StringBuilder.writeSafe(vertexBuilder, ')');
}
// normal
for (let i = 0; i < vertexCount; ++i) {
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
StringBuilder.writeSafe(normalBuilder, ',');
StringBuilder.writeFloat(normalBuilder, tmpV[1], 100);
StringBuilder.writeSafe(normalBuilder, ',');
StringBuilder.writeFloat(normalBuilder, tmpV[2], 100);
StringBuilder.writeSafe(normalBuilder, ')');
}
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
// face
for (let i = 0; i < drawCount; ++i) {
const v = isGeoTexture ? i : indices![i];
if (i > 0) StringBuilder.writeSafe(indexBuilder, ',');
StringBuilder.writeInteger(indexBuilder, v);
}
// color
const quantizedColors = new Uint8Array(drawCount * 3);
for (let i = 0; i < drawCount; i += 3) {
const v = isGeoTexture ? i : indices![i];
const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
UsdzExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
// material
const faceIndicesByMaterial = new Map<number, number[]>();
for (let i = 0; i < drawCount; i += 3) {
const color = Color.fromArray(quantizedColors, i);
const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
this.addMaterial(color, alpha);
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
let faceIndices = faceIndicesByMaterial.get(materialKey);
if (faceIndices === undefined) {
faceIndices = [];
faceIndicesByMaterial.set(materialKey, faceIndices);
}
faceIndices.push(i / 3);
}
// If this mesh uses only one material, bind it to the material directly.
// Otherwise, use GeomSubsets to bind it to multiple materials.
let materialBinding: string;
if (faceIndicesByMaterial.size === 1) {
const materialKey = faceIndicesByMaterial.keys().next().value;
materialBinding = `rel material:binding = </material${materialKey}>`;
} else {
const geomSubsets: string[] = [];
faceIndicesByMaterial.forEach((faceIndices: number[], materialKey: number) => {
geomSubsets.push(`
def GeomSubset "g${materialKey}"
{
uniform token elementType = "face"
uniform token familyName = "materialBind"
int[] indices = [${faceIndices.join(',')}]
rel material:binding = </material${materialKey}>
}
`);
});
materialBinding = geomSubsets.join('');
}
this.meshes.push(`
def Mesh "mesh${this.meshes.length}"
{
int[] faceVertexCounts = [${new Array(drawCount / 3).fill(3).join(',')}]
int[] faceVertexIndices = [${StringBuilder.getString(indexBuilder)}]
point3f[] points = [${StringBuilder.getString(vertexBuilder)}]
normal3f[] primvars:normals = [${StringBuilder.getString(normalBuilder)}] (
interpolation = "vertex"
)
uniform token subdivisionScheme = "none"
${materialBinding}
}
`);
}
}
async getData(ctx: RuntimeContext) {
const header = `#usda 1.0
(
customLayerData = {
string creator = "Mol* ${PLUGIN_VERSION}"
}
metersPerUnit = 1
)
`;
const usda = [header, ...this.materials, ...this.meshes].join('');
const usdaData = new Uint8Array(usda.length);
asciiWrite(usdaData, usda);
const zipDataObj = {
['model.usda']: usdaData
};
return {
usdz: await zip(ctx, zipDataObj, true)
};
}
async getBlob(ctx: RuntimeContext) {
const { usdz } = await this.getData(ctx);
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
}
constructor(private style: Style, boundingBox: Box3D, radius: number) {
super();
const t = Mat4();
// scale the model so that it fits within 1 meter
Mat4.fromUniformScaling(t, Math.min(1 / (radius * 2), 1));
// translate the model so that it sits on the ground plane (y = 0)
Mat4.translate(t, t, Vec3.create(
-(boundingBox.min[0] + boundingBox.max[0]) / 2,
-boundingBox.min[1],
-(boundingBox.min[2] + boundingBox.max[2]) / 2
));
this.centerTransform = t;
}
}

View File

@@ -73,7 +73,7 @@ export class Mp4Controls extends PluginComponent {
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
} catch (e) {
this.plugin.log.error('' + e);
this.plugin.log.error('Error during animation export');
throw e;
}
});

View File

@@ -115,7 +115,8 @@ export class Mp4EncoderUI extends CollapsableControls<{}, State> {
this.setState({ busy: true });
const data = await this.controls.render();
this.setState({ busy: false, data });
} catch {
} catch (e) {
console.error(e);
this.setState({ busy: false });
}
}

View File

@@ -62,7 +62,7 @@ export namespace PDBePreferredAssembly {
if (model.customProperties.has(Descriptor)) return true;
let asmName: string | undefined = fromCifData(model);
if (asmName === void 0 && params.PDBe_apiSourceJson) {
if (asmName === void 0 && params.PDBe_apiSourceJson) {
const data = await params.PDBe_apiSourceJson(model);
if (!data) return false;
asmName = asmNameFromJson(model, data);

View File

@@ -52,7 +52,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
}
update(p: { autoAttach: boolean, showTooltip: boolean }) {
let updated = this.params.autoAttach !== p.autoAttach;
const updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.params.showTooltip = p.showTooltip;
this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);

View File

@@ -79,6 +79,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
return {
factory: StructureQualityReportColorTheme,
granularity: 'group',
preferSmoothing: true,
color: color,
props: props,
description: 'Assigns residue colors according to the number of quality issues or a specific quality issue. Data from wwPDB Validation Report, obtained via PDBe.',
@@ -86,7 +87,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
};
}
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
name: 'pdbe-structure-quality-report',
label: 'Structure Quality Report',
category: ColorTheme.Category.Validation,
@@ -114,6 +115,6 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
detach: (data) => data.structure && StructureQualityReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -73,7 +73,7 @@ namespace StructureQualityReport {
}
export function fromCif(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): StructureQualityReport | undefined {
let info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
const info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
if (!info) return;
const data = getCifData(model);
const issueMap = createIssueMapFromCif(model, data.residues, data.groups);

View File

@@ -47,7 +47,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
}
update(p: { autoAttach: boolean }) {
let updated = this.params.autoAttach !== p.autoAttach;
const updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
return updated;
@@ -85,7 +85,7 @@ export const InitAssemblySymmetry3D = StateAction.build({
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
} catch(e) {
} catch (e) {
plugin.log.error(`Assembly Symmetry: ${e}`);
return;
}

View File

@@ -109,6 +109,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
detach: (data) => data.structure && AssemblySymmetryProvider.ref(data.structure, false)
}
};

View File

@@ -124,16 +124,20 @@ export function getSymmetrySelectParam(structure?: Structure) {
if (structure) {
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
if (assemblySymmetryData) {
const options: [number, string][] = [];
const options: [number, string][] = [
[-1, 'Off']
];
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
const { symbol, kind } = assemblySymmetryData[i];
if (symbol !== 'C1') {
options.push([ i, `${i + 1}: ${symbol} ${kind}` ]);
options.push([i, `${i + 1}: ${symbol} ${kind}`]);
}
}
if (options.length) {
if (options.length > 1) {
param.options = options;
param.defaultValue = options[0][0];
param.defaultValue = options[1][0];
} else {
options.length = 0;
}
}
}

View File

@@ -310,7 +310,7 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
}
}
const unitCircleDirections = (function() {
const unitCircleDirections = (function () {
const dirs: Vec3[] = [];
const circle = polygon(12, false, 1);
for (let i = 0, il = circle.length; i < il; i += 3) {

View File

@@ -7,7 +7,7 @@
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { StructureHierarchyManager } from '../../../mol-plugin-state/manager/structure/hierarchy';
@@ -73,7 +73,6 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
const structure = this.pivot.cell.obj?.data;
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
params.serverUrl.isHidden = true;
params.symmetryIndex.options = [[-1, 'Off'], ...params.symmetryIndex.options];
return params;
}

View File

@@ -1,12 +1,13 @@
schema: https://data.rcsb.org/graphql
documents: './src/extensions/rcsb/graphql/symmetry.gql.ts'
generates:
'./src/extensions/rcsb/graphql/types.ts':
plugins:
- add: '/* eslint-disable */'
- time
- typescript
- typescript-operations
config:
immutableTypes: true
skipTypename: true
'./src/extensions/rcsb/graphql/types.ts':
plugins:
- add:
content: '/* eslint-disable */'
- time
- typescript
- typescript-operations
config:
immutableTypes: true
skipTypename: true

File diff suppressed because it is too large Load Diff

View File

@@ -63,7 +63,7 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
}
update(p: { autoAttach: boolean, showTooltip: boolean }) {
let updated = this.params.autoAttach !== p.autoAttach;
const updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.params.showTooltip = p.showTooltip;
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);

View File

@@ -58,6 +58,7 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
return {
factory: DensityFitColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
props,
contextHash,
@@ -76,6 +77,6 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromXray(ctx.structure.models[0]) && Model.probablyHasDensityMap(ctx.structure.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -96,6 +96,7 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
return {
factory: GeometryQualityColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
props,
contextHash,
@@ -114,6 +115,6 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -49,6 +49,7 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
return {
factory: RandomCoilIndexColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
props,
contextHash,
@@ -67,6 +68,6 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromNmr(ctx.structure.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -112,7 +112,7 @@ namespace ValidationReport {
}
export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<CustomProperty.Data<ValidationReport>> {
switch(props.source.name) {
switch (props.source.name) {
case 'file': return open(ctx, model, props.source.params);
case 'server': return fetch(ctx, model, props.source.params);
}
@@ -208,8 +208,8 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
for (let i = 0, il = clashes.edgeCount * 2; i < il; ++i) {
// TODO create lookup
let indexA = SortedArray.indexOf(elementsA, a[i]);
let indexB = SortedArray.indexOf(elementsB, b[i]);
const indexA = SortedArray.indexOf(elementsA, a[i]);
const indexB = SortedArray.indexOf(elementsB, b[i]);
if (indexA !== -1 && indexB !== -1) {
unitA.conformation.position(a[i], pA);
@@ -250,8 +250,8 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
for (let i = 0, il = edgeCount * 2; i < il; ++i) {
// TODO create lookup
let indexA = SortedArray.indexOf(elements, a[i]);
let indexB = SortedArray.indexOf(elements, b[i]);
const indexA = SortedArray.indexOf(elements, a[i]);
const indexB = SortedArray.indexOf(elements, b[i]);
if (indexA !== -1 && indexB !== -1) {
unit.conformation.position(a[i], pA);
@@ -431,7 +431,7 @@ function parseValidationReportXml(xml: XMLDocument, model: Model): ValidationRep
const groups = xml.getElementsByTagName('ModelledSubgroup');
for (let i = 0, il = groups.length; i < il; ++i) {
const g = groups[ i ];
const g = groups[i];
const ga = g.attributes;
const pdbx_PDB_model_num = parseInt(getItem(ga, 'model'));

View File

@@ -16,7 +16,7 @@ import { RepresentationContext, RepresentationParamsGetter, Representation } fro
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
import { VisualContext } from '../../../mol-repr/visual';
import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from './prop';
@@ -28,6 +28,7 @@ import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
import { Sphere3D } from '../../../mol-math/geometry';
import { bondLabel } from '../../../mol-theme/label';
import { getUnitKindsParam } from '../../../mol-repr/structure/params';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
//
@@ -106,7 +107,7 @@ function getIntraClashLabel(structure: Structure, unit: Unit.Atomic, clashes: In
function IntraClashLoci(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
return DataLoci('intra-clashes', { unit, clashes }, elements,
(boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere),
(boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere),
() => getIntraClashLabel(structure, unit, clashes, elements));
}
@@ -124,7 +125,7 @@ function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup,
}
function eachIntraClash(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
let changed = false;
const changed = false;
// TODO
return changed;
}
@@ -225,7 +226,7 @@ function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, ele
function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
return DataLoci('inter-clashes', clashes, elements,
(boundingSphere: Sphere3D) => getInterClashBoundingSphere(structure, clashes, elements, boundingSphere),
(boundingSphere: Sphere3D) => getInterClashBoundingSphere(structure, clashes, elements, boundingSphere),
() => getInterClashLabel(structure, clashes, elements));
}
@@ -239,7 +240,7 @@ function getInterClashLoci(pickingId: PickingId, structure: Structure, id: numbe
}
function eachInterClash(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
let changed = false;
const changed = false;
// TODO
return changed;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -27,6 +27,10 @@ interface ICamera {
readonly fogNear: number,
}
const tmpPos1 = Vec3();
const tmpPos2 = Vec3();
const tmpClip = Vec4();
class Camera implements ICamera {
readonly view: Mat4 = Mat4.identity();
readonly projection: Mat4 = Mat4.identity();
@@ -34,7 +38,7 @@ class Camera implements ICamera {
readonly inverseProjectionView: Mat4 = Mat4.identity();
private pixelScale: number
get pixelRatio () {
get pixelRatio() {
const dpr = (typeof window !== 'undefined') ? window.devicePixelRatio : 1;
return dpr * this.pixelScale;
}
@@ -87,7 +91,12 @@ class Camera implements ICamera {
if (changed) {
Mat4.mul(this.projectionView, this.projection, this.view);
Mat4.invert(this.inverseProjectionView, this.projectionView);
if (!Mat4.tryInvert(this.inverseProjectionView, this.projectionView)) {
Mat4.copy(this.view, this.prevView);
Mat4.copy(this.projection, this.prevProjection);
Mat4.mul(this.projectionView, this.projection, this.view);
return false;
}
Mat4.copy(this.prevView, this.view);
Mat4.copy(this.prevProjection, this.projection);
@@ -150,14 +159,32 @@ class Camera implements ICamera {
}
}
/** Transform point into 2D window coordinates. */
project(out: Vec4, point: Vec3) {
return cameraProject(out, point, this.viewport, this.projectionView);
}
unproject(out: Vec3, point: Vec3) {
/**
* Transform point from screen space to 3D coordinates.
* The point must have `x` and `y` set to 2D window coordinates
* and `z` between 0 (near) and 1 (far); the optional `w` is not used.
*/
unproject(out: Vec3, point: Vec3 | Vec4) {
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView);
}
/** World space pixel size at given `point` */
getPixelSize(point: Vec3) {
// project -> unproject of `point` does not exactly return the same
// to get a sufficiently accurate measure we unproject the original
// clip position in addition to the one shifted bey one pixel
this.project(tmpClip, point);
this.unproject(tmpPos1, tmpClip);
tmpClip[0] += 1;
this.unproject(tmpPos2, tmpClip);
return Vec3.distance(tmpPos1, tmpPos2);
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(0, 0, 128, 128), props: Partial<{ pixelScale: number }> = {}) {
this.viewport = viewport;
this.pixelScale = props.pixelScale || 1;
@@ -173,7 +200,7 @@ namespace Camera {
/**
* Sets an offseted view in a larger frustum. This is useful for
* - multi-window or multi-monitor/multi-machine setups
* - jittering the camera position for
* - jittering the camera position for sampling
*/
export interface ViewOffset {
enabled: boolean,
@@ -230,7 +257,7 @@ namespace Camera {
target: Vec3.create(0, 0, 0),
radius: 0,
radiusMax: 0,
radiusMax: 10,
fog: 50,
clipFar: true
};
@@ -267,6 +294,18 @@ namespace Camera {
return out;
}
export function areSnapshotsEqual(a: Snapshot, b: Snapshot) {
return a.mode === b.mode
&& a.fov === b.fov
&& a.radius === b.radius
&& a.radiusMax === b.radiusMax
&& a.fog === b.fog
&& a.clipFar === b.clipFar
&& Vec3.exactEquals(a.position, b.position)
&& Vec3.exactEquals(a.up, b.up)
&& Vec3.exactEquals(a.target, b.target);
}
}
function updateOrtho(camera: Camera) {
@@ -347,8 +386,9 @@ function updateClip(camera: Camera) {
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
} else {
near = Math.max(0, near);
far = Math.max(0, far);
// not too close to 0 as it causes issues with outline rendering
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
}
if (near === far) {

View File

@@ -39,6 +39,9 @@ class CameraTransitionManager {
this._target.radius = this._target.radiusMax;
}
if (this._target.radius < 0.01) this._target.radius = 0.01;
if (this._target.radiusMax < 0.01) this._target.radiusMax = 0.01;
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(this._target);
return;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -55,14 +55,11 @@ namespace Viewport {
//
const NEAR_RANGE = 0;
const FAR_RANGE = 1;
const tmpVec4 = Vec4();
/** Transform point into 2D window coordinates. */
export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
export function cameraProject(out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {
const { x, y, width, height } = viewport;
// clip space -> NDC -> window coordinates, implicit 1.0 for w component
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0);
@@ -78,27 +75,28 @@ export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, proje
tmpVec4[2] /= w;
}
// transform into window coordinates, set fourth component is (1/clip.w) as in gl_FragCoord.w
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2);
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2);
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2;
// transform into window coordinates, set fourth component to 1 / clip.w as in gl_FragCoord.w
out[0] = (tmpVec4[0] + 1) * width * 0.5 + x;
out[1] = (1 - tmpVec4[1]) * height * 0.5 + y; // flip Y
out[2] = (tmpVec4[2] + 1) * 0.5;
out[3] = w === 0 ? 0 : 1 / w;
return out;
}
/**
* Transform point from screen space to 3D coordinates.
* The point must have x and y set to 2D window coordinates and z between 0 (near) and 1 (far).
* The point must have `x` and `y` set to 2D window coordinates
* and `z` between 0 (near) and 1 (far); the optional `w` is not used.
*/
export function cameraUnproject (out: Vec3, point: Vec3, viewport: Viewport, inverseProjectionView: Mat4) {
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
export function cameraUnproject(out: Vec3, point: Vec3 | Vec4, viewport: Viewport, inverseProjectionView: Mat4) {
const { x, y, width, height } = viewport;
const x = point[0] - vX;
const y = (vHeight - point[1] - 1) - vY;
const z = point[2];
const px = point[0] - x;
const py = (height - point[1] - 1) - y;
const pz = point[2];
out[0] = (2 * x) / vWidth - 1;
out[1] = (2 * y) / vHeight - 1;
out[2] = 2 * z - 1;
out[0] = (2 * px) / width - 1;
out[1] = (2 * py) / height - 1;
out[2] = 2 * pz - 1;
return Vec3.transformMat4(out, out, inverseProjectionView);
}

View File

@@ -23,7 +23,7 @@ import { Camera } from './camera';
import { ParamDefinition as PD } from '../mol-util/param-definition';
import { DebugHelperParams } from './helper/bounding-sphere-helper';
import { SetUtils } from '../mol-util/set';
import { Canvas3dInteractionHelper } from './helper/interaction-events';
import { Canvas3dInteractionHelper, Canvas3dInteractionHelperParams } from './helper/interaction-events';
import { PostprocessingParams } from './passes/postprocessing';
import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PickData } from './passes/pick';
@@ -38,6 +38,7 @@ import { StereoCamera, StereoCameraParams } from './camera/stereo';
import { Helper } from './helper/helper';
import { Passes } from './passes/passes';
import { shallowEqual } from '../mol-util';
import { MarkingParams } from './passes/marking';
export const Canvas3DParams = {
camera: PD.Group({
@@ -61,11 +62,17 @@ export const Canvas3DParams = {
}, { pivot: 'radius' }),
viewport: PD.MappedStatic('canvas', {
canvas: PD.Group({}),
custom: PD.Group({
'static-frame': PD.Group({
x: PD.Numeric(0),
y: PD.Numeric(0),
width: PD.Numeric(128),
height: PD.Numeric(128)
}),
'relative-frame': PD.Group({
x: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
y: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
width: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 }),
height: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 })
})
}),
@@ -74,8 +81,10 @@ export const Canvas3DParams = {
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
marking: PD.Group(MarkingParams),
renderer: PD.Group(RendererParams),
trackball: PD.Group(TrackballControlsParams),
interaction: PD.Group(Canvas3dInteractionHelperParams),
debug: PD.Group(DebugHelperParams),
handle: PD.Group(HandleHelperParams),
};
@@ -100,30 +109,34 @@ interface Canvas3DContext {
}
namespace Canvas3DContext {
const DefaultAttribs = {
export const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
preserveDrawingBuffer: true,
pixelScale: 1,
pickScale: 0.25,
enableWboit: true
/** extra pixels to around target to check in case target is empty */
pickPadding: 1,
enableWboit: true,
preferWebGl1: false
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
const { antialias, preserveDrawingBuffer, pixelScale } = a;
const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const gl = getGLContext(canvas, {
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
depth: true, // the renderer requires a depth buffer
premultipliedAlpha: true, // the renderer outputs PMA
preferWebGl1
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const input = InputObserver.fromElement(canvas, { pixelScale });
const input = InputObserver.fromElement(canvas, { pixelScale, preventGestures: true });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
@@ -201,7 +214,7 @@ interface Canvas3D {
*/
commit(isSynchronous?: boolean): void
/**
* Funcion for external "animation" control
* Function for external "animation" control
* Calls commit.
*/
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
@@ -214,9 +227,15 @@ interface Canvas3D {
/** Reset the timers, used by "animate" */
resetTime(t: number): void
animate(): void
pause(): void
/**
* Pause animation loop and optionally any rendering
* @param noDraw pause any rendering (drawPaused = true)
*/
pause(noDraw?: boolean): void
/** Sets drawPaused = false without starting the built in animation loop */
resume(): void
identify(x: number, y: number): PickData | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void
getLoci(pickingId: PickingId | undefined): Representation.Loci
notifyDidDraw: boolean,
@@ -232,6 +251,7 @@ interface Canvas3D {
requestCameraReset(options?: { durationMs?: number, snapshot?: Camera.SnapshotProvider }): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
readonly boundingSphereVisible: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
getImagePass(props: Partial<ImageProps>): ImagePass
getRenderObjects(): GraphicsRenderObject[]
@@ -290,11 +310,10 @@ namespace Canvas3D {
const renderer = Renderer.create(webgl, p.renderer);
const helper = new Helper(webgl, scene, p);
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height });
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height }, attribs.pickPadding);
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, p.interaction);
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
let drawPending = false;
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
@@ -325,7 +344,7 @@ namespace Canvas3D {
return { loci, repr };
}
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) {
const { repr, loci } = reprLoci;
let changed = false;
if (repr) {
@@ -335,7 +354,7 @@ namespace Canvas3D {
changed = helper.camera.mark(loci, action) || changed;
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed) {
if (changed && !noDraw) {
scene.update(void 0, true);
helper.handle.scene.update(void 0, true);
helper.camera.scene.update(void 0, true);
@@ -362,9 +381,13 @@ namespace Canvas3D {
let didRender = false;
controls.update(currentTime);
const cameraChanged = camera.update();
const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
if (resized || force || cameraChanged || multiSampleChanged) {
const shouldRender = force || cameraChanged || resized || forceNextRender;
forceNextRender = false;
const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample);
if (shouldRender || multiSampleChanged) {
let cam: Camera | StereoCamera = camera;
if (p.camera.stereo.name === 'on') {
stereoCamera.update();
@@ -374,7 +397,7 @@ namespace Canvas3D {
if (MultiSamplePass.isEnabled(p.multiSample)) {
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
} else {
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing);
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking);
}
pickHelper.dirty = true;
didRender = true;
@@ -383,22 +406,20 @@ namespace Canvas3D {
return didRender;
}
let forceNextDraw = false;
let forceNextRender = false;
let forceDrawAfterAllCommited = false;
let currentTime = 0;
let drawPaused = false;
function draw(force?: boolean) {
if (render(!!force || forceNextDraw) && notifyDidDraw) {
if (drawPaused) return;
if (render(!!force) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
forceNextDraw = false;
drawPending = false;
}
function requestDraw(force?: boolean) {
if (drawPending) return;
drawPending = true;
forceNextDraw = !!force;
forceNextRender = forceNextRender || !!force;
}
let animationFrameHandle = 0;
@@ -429,11 +450,13 @@ namespace Canvas3D {
}
function animate() {
drawPaused = false;
controls.start(now());
if (animationFrameHandle === 0) _animate();
}
function pause() {
function pause(noDraw = false) {
drawPaused = noDraw;
cancelAnimationFrame(animationFrameHandle);
animationFrameHandle = 0;
}
@@ -620,9 +643,11 @@ namespace Canvas3D {
viewport: p.viewport,
postprocessing: { ...p.postprocessing },
marking: { ...p.marking },
multiSample: { ...p.multiSample },
renderer: { ...renderer.props },
trackball: { ...controls.props },
interaction: { ...interactionHelper.props },
debug: { ...helper.debug.props },
handle: { ...helper.handle.props },
};
@@ -688,6 +713,7 @@ namespace Canvas3D {
animate,
resetTime,
pause,
resume: () => { drawPaused = false; },
identify,
mark,
getLoci,
@@ -703,6 +729,7 @@ namespace Canvas3D {
},
camera,
boundingSphere: scene.boundingSphere,
boundingSphereVisible: scene.boundingSphereVisible,
get notifyDidDraw() { return notifyDidDraw; },
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
@@ -711,7 +738,7 @@ namespace Canvas3D {
resized,
setProps: (properties, doNotRequestDraw = false) => {
const props: PartialCanvas3DProps = typeof properties === 'function'
? produce(getProps(), properties)
? produce(getProps(), properties as any)
: properties;
const cameraState: Partial<Camera.Snapshot> = Object.create(null);
@@ -753,9 +780,11 @@ namespace Canvas3D {
}
if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing);
if (props.marking) Object.assign(p.marking, props.marking);
if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
if (props.renderer) renderer.setProps(props.renderer);
if (props.trackball) controls.setProps(props.trackball);
if (props.interaction) interactionHelper.setProps(props.interaction);
if (props.debug) helper.debug.setProps(props.debug);
if (props.handle) helper.handle.setProps(props.handle);
@@ -800,16 +829,27 @@ namespace Canvas3D {
};
function updateViewport() {
const oldX = x, oldY = y, oldWidth = width, oldHeight = height;
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
} else {
} else if (p.viewport.name === 'static-frame') {
x = p.viewport.params.x * webgl.pixelRatio;
y = p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
height = p.viewport.params.height * webgl.pixelRatio;
y = gl.drawingBufferHeight - height - p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
} else if (p.viewport.name === 'relative-frame') {
x = Math.round(p.viewport.params.x * gl.drawingBufferWidth);
height = Math.round(p.viewport.params.height * gl.drawingBufferHeight);
y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight);
width = Math.round(p.viewport.params.width * gl.drawingBufferWidth);
}
if (oldX !== x || oldY !== y || oldWidth !== width || oldHeight !== height) {
forceNextRender = true;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -10,7 +10,7 @@
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { Viewport } from '../camera/util';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
@@ -49,6 +49,9 @@ export const TrackballControlsParams = {
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
gestureScaleFactor: PD.Numeric(1, {}, { isHidden: true }),
maxWheelDelta: PD.Numeric(0.02, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
/**
@@ -91,6 +94,7 @@ namespace TrackballControls {
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
const wheelSub = input.wheel.subscribe(onWheel);
const pinchSub = input.pinch.subscribe(onPinch);
const gestureSub = input.gesture.subscribe(onGesture);
let _isInteracting = false;
@@ -390,25 +394,33 @@ namespace TrackballControls {
_isInteracting = false;
}
function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
function onWheel({ x, y, spinX, spinY, dz, buttons, modifiers }: WheelInput) {
if (outsideViewport(x, y)) return;
const delta = absMax(dx, dy, dz);
let delta = absMax(spinX * 0.075, spinY * 0.075, dz * 0.0001);
if (delta < -p.maxWheelDelta) delta = -p.maxWheelDelta;
else if (delta > p.maxWheelDelta) delta = p.maxWheelDelta;
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001;
_zoomEnd[1] += delta;
}
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
_focusEnd[1] += delta * 0.0001;
_focusEnd[1] += delta;
}
}
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
function onPinch({ fractionDelta, buttons, modifiers }: PinchInput) {
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_isInteracting = true;
_zoomEnd[1] += (fraction - 1) * 0.1;
_zoomEnd[1] += p.gestureScaleFactor * fractionDelta;
}
}
function onGesture({ deltaScale }: GestureInput) {
_isInteracting = true;
_zoomEnd[1] += p.gestureScaleFactor * deltaScale;
}
function dispose() {
if (disposed) return;
disposed = true;
@@ -416,6 +428,7 @@ namespace TrackballControls {
dragSub.unsubscribe();
wheelSub.unsubscribe();
pinchSub.unsubscribe();
gestureSub.unsubscribe();
interactionEndSub.unsubscribe();
}

View File

@@ -121,7 +121,7 @@ export class BoundingSphereHelper {
}
get props() { return this._props as Readonly<DebugHelperProps>; }
setProps (props: Partial<DebugHelperProps>) {
setProps(props: Partial<DebugHelperProps>) {
Object.assign(this._props, props);
if (this.isEnabled) this.update();
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -11,6 +11,8 @@ import { InputObserver, ModifiersKeys, ButtonsType } from '../../mol-util/input/
import { RxEventHelper } from '../../mol-util/rx-event-helper';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Camera } from '../camera';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Bond } from '../../mol-model/structure';
type Canvas3D = import('../canvas3d').Canvas3D
type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
@@ -19,6 +21,17 @@ type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
const enum InputEvent { Move, Click, Drag }
const tmpPosA = Vec3();
const tmpPos = Vec3();
const tmpNorm = Vec3();
export const Canvas3dInteractionHelperParams = {
maxFps: PD.Numeric(30, { min: 10, max: 60, step: 10 }),
preferAtomPixelPadding: PD.Numeric(3, { min: 0, max: 20, step: 1 }, { description: 'Number of extra pixels at which to prefer atoms over bonds.' }),
};
export type Canvas3dInteractionHelperParams = typeof Canvas3dInteractionHelperParams
export type Canvas3dInteractionHelperProps = PD.Values<Canvas3dInteractionHelperParams>
export class Canvas3dInteractionHelper {
private ev = RxEventHelper.create();
@@ -48,6 +61,12 @@ export class Canvas3dInteractionHelper {
private button: ButtonsType.Flag = ButtonsType.create(0);
private modifiers: ModifiersKeys = ModifiersKeys.None;
readonly props: Canvas3dInteractionHelperProps;
setProps(props: Partial<Canvas3dInteractionHelperProps>) {
Object.assign(this.props, props);
}
private identify(e: InputEvent, t: number) {
const xyChanged = this.startX !== this.endX || this.startY !== this.endY;
@@ -70,7 +89,7 @@ export class Canvas3dInteractionHelper {
}
if (e === InputEvent.Click) {
const loci = this.getLoci(this.id);
const loci = this.getLoci(this.id, this.position);
this.events.click.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
this.prevLoci = loci;
return;
@@ -78,13 +97,13 @@ export class Canvas3dInteractionHelper {
if (!this.inside || this.currentIdentifyT !== t || !xyChanged || this.outsideViewport(this.endX, this.endY)) return;
const loci = this.getLoci(this.id);
const loci = this.getLoci(this.id, this.position);
this.events.hover.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
this.prevLoci = loci;
}
tick(t: number) {
if (this.inside && t - this.prevT > 1000 / this.maxFps) {
if (this.inside && t - this.prevT > 1000 / this.props.maxFps) {
this.prevT = t;
this.currentIdentifyT = t;
this.identify(this.isInteracting ? InputEvent.Drag : InputEvent.Move, t);
@@ -144,18 +163,41 @@ export class Canvas3dInteractionHelper {
);
}
private getLoci(pickingId: PickingId | undefined, position: Vec3 | undefined) {
const { repr, loci } = this.lociGetter(pickingId);
if (position && repr && Bond.isLoci(loci) && loci.bonds.length === 2) {
const { aUnit, aIndex } = loci.bonds[0];
aUnit.conformation.position(aUnit.elements[aIndex], tmpPosA);
Vec3.sub(tmpNorm, this.camera.state.position, this.camera.state.target);
Vec3.projectPointOnPlane(tmpPos, position, tmpNorm, tmpPosA);
const pixelSize = this.camera.getPixelSize(tmpPos);
let radius = repr.theme.size.size(loci.bonds[0]) * (repr.props.sizeFactor ?? 1);
if (repr.props.lineSizeAttenuation === false) {
// divide by two to get radius
radius *= pixelSize / 2;
}
radius += this.props.preferAtomPixelPadding * pixelSize;
if (Vec3.distance(tmpPos, tmpPosA) < radius) {
return { repr, loci: Bond.toFirstStructureElementLoci(loci) };
}
}
return { repr, loci };
}
dispose() {
this.ev.dispose();
}
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, private maxFps: number = 30) {
input.drag.subscribe(({x, y, buttons, button, modifiers }) => {
constructor(private canvasIdentify: Canvas3D['identify'], private lociGetter: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, props: Partial<Canvas3dInteractionHelperProps> = {}) {
this.props = { ...PD.getDefaultValues(Canvas3dInteractionHelperParams), ...props };
input.drag.subscribe(({ x, y, buttons, button, modifiers }) => {
this.isInteracting = true;
// console.log('drag');
this.drag(x, y, buttons, button, modifiers);
});
input.move.subscribe(({x, y, inside, buttons, button, modifiers }) => {
input.move.subscribe(({ x, y, inside, buttons, button, modifiers }) => {
if (!inside || this.isInteracting) return;
// console.log('move');
this.move(x, y, buttons, button, modifiers);
@@ -166,7 +208,7 @@ export class Canvas3dInteractionHelper {
this.leave();
});
input.click.subscribe(({x, y, buttons, button, modifiers }) => {
input.click.subscribe(({ x, y, buttons, button, modifiers }) => {
if (this.outsideViewport(x, y)) return;
// console.log('click');
this.click(x, y, buttons, button, modifiers);

View File

@@ -22,10 +22,11 @@ import { Helper } from '../helper/helper';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { depthMerge_frag } from '../../mol-gl/shader/depth-merge.frag';
import { copy_frag } from '../../mol-gl/shader/copy.frag';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
import { MarkingPass, MarkingProps } from './marking';
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
const DepthMergeSchema = {
...QuadSchema,
@@ -52,27 +53,6 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
return createComputeRenderable(renderItem, values);
}
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
function getCopyRenderable(ctx: WebGLContext, colorTexture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export class DrawPass {
private readonly drawTarget: RenderTarget
@@ -92,6 +72,7 @@ export class DrawPass {
private copyFboPostprocessing: CopyRenderable
private wboit: WboitPass | undefined
private readonly marking: MarkingPass
readonly postprocessing: PostprocessingPass
private readonly antialiasing: AntialiasingPass
@@ -122,11 +103,12 @@ export class DrawPass {
this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth);
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
this.marking = new MarkingPass(webgl, width, height);
this.postprocessing = new PostprocessingPass(webgl, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.copyFboTarget = getCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = getCopyRenderable(webgl, this.postprocessing.target.texture);
this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = createCopyRenderable(webgl, this.postprocessing.target.texture);
}
reset() {
@@ -162,6 +144,7 @@ export class DrawPass {
this.wboit.setSize(width, height);
}
this.marking.setSize(width, height);
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
}
@@ -281,22 +264,28 @@ export class DrawPass {
renderer.renderBlendedTransparent(scene.primitives, camera, null);
}
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
const volumeRendering = scene.volumes.renderables.length > 0;
const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
const markingEnabled = MarkingPass.isEnabled(markingProps);
const { x, y, width, height } = camera.viewport;
renderer.setViewport(x, y, width, height);
renderer.update(camera);
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
this.drawTarget.bind();
renderer.clear(false);
}
if (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
} else {
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (postprocessingEnabled) {
this.postprocessing.target.bind();
} else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) {
this.colorTarget.bind();
@@ -304,6 +293,22 @@ export class DrawPass {
this.drawTarget.bind();
}
if (markingEnabled) {
const markingDepthTest = markingProps.ghostEdgeStrength < 1;
if (markingDepthTest) {
this.marking.depthTarget.bind();
renderer.clear(false);
renderer.renderMarkingDepth(scene.primitives, camera, null);
}
this.marking.maskTarget.bind();
renderer.clear(false);
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
this.marking.update(markingProps);
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
}
if (helper.debug.isEnabled) {
helper.debug.syncVisibility();
renderer.renderBlended(helper.debug.scene, camera, null);
@@ -323,7 +328,7 @@ export class DrawPass {
this.drawTarget.bind();
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (postprocessingEnabled) {
this.copyFboPostprocessing.render();
} else if (volumeRendering || this.wboitEnabled) {
this.copyFboTarget.render();
@@ -333,15 +338,16 @@ export class DrawPass {
this.webgl.gl.flush();
}
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
renderer.setTransparentBackground(transparentBackground);
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
renderer.setPixelRatio(this.webgl.pixelRatio);
if (StereoCamera.is(camera)) {
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -17,11 +17,13 @@ import { Viewport } from '../camera/util';
import { PixelData } from '../../mol-util/image';
import { Helper } from '../helper/helper';
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
import { MarkingParams } from './marking';
export const ImageParams = {
transparentBackground: PD.Boolean(false),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
marking: PD.Group(MarkingParams),
cameraHelper: PD.Group(CameraHelperParams),
};
@@ -85,7 +87,7 @@ export class ImagePass {
this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
this._colorTarget = this.multiSamplePass.colorTarget;
} else {
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing);
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking);
this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
}
}
@@ -103,7 +105,9 @@ export class ImagePass {
} else {
this.webgl.readPixels(viewport.x, height - viewport.y - viewport.height, w, h, array);
}
PixelData.flipY({ array, width: w, height: h });
const pixelData = PixelData.create(array, w, h);
PixelData.flipY(pixelData);
PixelData.divideByAlpha(pixelData);
return new ImageData(new Uint8ClampedArray(array), w, h);
}
}

View File

@@ -0,0 +1,194 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture } from '../../mol-gl/webgl/texture';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { overlay_frag } from '../../mol-gl/shader/marking/overlay.frag';
import { Viewport } from '../camera/util';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { Color } from '../../mol-util/color';
import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
export const MarkingParams = {
enabled: PD.Boolean(false),
highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),
ghostEdgeStrength: PD.Numeric(0.3, { min: 0, max: 1, step: 0.1 }, { description: 'Opacity of the hidden edges that are covered by other geometry. When set to 1, one less geometry render pass is done.' }),
innerEdgeFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Factor to multiply the inner edge color with - for added contrast.' }),
};
export type MarkingProps = PD.Values<typeof MarkingParams>
export class MarkingPass {
static isEnabled(props: MarkingProps) {
return props.enabled;
}
readonly depthTarget: RenderTarget
readonly maskTarget: RenderTarget
private readonly edgesTarget: RenderTarget
private readonly edge: EdgeRenderable
private readonly overlay: OverlayRenderable
constructor(private webgl: WebGLContext, width: number, height: number) {
this.depthTarget = webgl.createRenderTarget(width, height);
this.maskTarget = webgl.createRenderTarget(width, height);
this.edgesTarget = webgl.createRenderTarget(width, height);
this.edge = getEdgeRenderable(webgl, this.maskTarget.texture);
this.overlay = getOverlayRenderable(webgl, this.edgesTarget.texture);
}
private setEdgeState(viewport: Viewport) {
const { gl, state } = this.webgl;
state.enable(gl.SCISSOR_TEST);
state.enable(gl.BLEND);
state.blendFunc(gl.ONE, gl.ONE);
state.blendEquation(gl.FUNC_ADD);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
private setOverlayState(viewport: Viewport) {
const { gl, state } = this.webgl;
state.enable(gl.SCISSOR_TEST);
state.enable(gl.BLEND);
state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
state.blendEquation(gl.FUNC_ADD);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
}
setSize(width: number, height: number) {
const w = this.depthTarget.getWidth();
const h = this.depthTarget.getHeight();
if (width !== w || height !== h) {
this.depthTarget.setSize(width, height);
this.maskTarget.setSize(width, height);
this.edgesTarget.setSize(width, height);
ValueCell.update(this.edge.values.uTexSizeInv, Vec2.set(this.edge.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
ValueCell.update(this.overlay.values.uTexSizeInv, Vec2.set(this.overlay.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
}
}
update(props: MarkingProps) {
const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props;
const { values: edgeValues } = this.edge;
const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio);
if (edgeValues.dEdgeScale.ref.value !== _edgeScale) {
ValueCell.update(edgeValues.dEdgeScale, _edgeScale);
this.edge.update();
}
const { values: overlayValues } = this.overlay;
ValueCell.update(overlayValues.uHighlightEdgeColor, Color.toVec3Normalized(overlayValues.uHighlightEdgeColor.ref.value, highlightEdgeColor));
ValueCell.update(overlayValues.uSelectEdgeColor, Color.toVec3Normalized(overlayValues.uSelectEdgeColor.ref.value, selectEdgeColor));
ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
}
render(viewport: Viewport, target: RenderTarget | undefined) {
this.edgesTarget.bind();
this.setEdgeState(viewport);
this.edge.render();
if (target) {
target.bind();
} else {
this.webgl.unbindFramebuffer();
}
this.setOverlayState(viewport);
this.overlay.render();
}
}
//
const EdgeSchema = {
...QuadSchema,
tMaskTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
dEdgeScale: DefineSpec('number'),
};
const EdgeShaderCode = ShaderCode('edge', quad_vert, edge_frag);
type EdgeRenderable = ComputeRenderable<Values<typeof EdgeSchema>>
function getEdgeRenderable(ctx: WebGLContext, maskTexture: Texture): EdgeRenderable {
const width = maskTexture.getWidth();
const height = maskTexture.getHeight();
const values: Values<typeof EdgeSchema> = {
...QuadValues,
tMaskTexture: ValueCell.create(maskTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
dEdgeScale: ValueCell.create(1),
};
const schema = { ...EdgeSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', EdgeShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
//
const OverlaySchema = {
...QuadSchema,
tEdgeTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
uHighlightEdgeColor: UniformSpec('v3'),
uSelectEdgeColor: UniformSpec('v3'),
uGhostEdgeStrength: UniformSpec('f'),
uInnerEdgeFactor: UniformSpec('f'),
};
const OverlayShaderCode = ShaderCode('overlay', quad_vert, overlay_frag);
type OverlayRenderable = ComputeRenderable<Values<typeof OverlaySchema>>
function getOverlayRenderable(ctx: WebGLContext, edgeTexture: Texture): OverlayRenderable {
const width = edgeTexture.getWidth();
const height = edgeTexture.getHeight();
const values: Values<typeof OverlaySchema> = {
...QuadValues,
tEdgeTexture: ValueCell.create(edgeTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
uHighlightEdgeColor: ValueCell.create(Vec3()),
uSelectEdgeColor: ValueCell.create(Vec3()),
uGhostEdgeStrength: ValueCell.create(0),
uInnerEdgeFactor: ValueCell.create(0),
};
const schema = { ...OverlaySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', OverlayShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

View File

@@ -22,9 +22,9 @@ import { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { Helper } from '../helper/helper';
import { StereoCamera } from '../camera/stereo';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { compose_frag } from '../../mol-gl/shader/compose.frag';
import { MarkingProps } from './marking';
const ComposeSchema = {
...QuadSchema,
@@ -55,7 +55,11 @@ export const MultiSampleParams = {
};
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
type Props = { multiSample: MultiSampleProps, postprocessing: PostprocessingProps }
type Props = {
multiSample: MultiSampleProps
postprocessing: PostprocessingProps
marking: MarkingProps
}
export class MultiSamplePass {
static isEnabled(props: MultiSampleProps) {
@@ -119,7 +123,7 @@ export class MultiSamplePass {
//
// This manual approach to MSAA re-renders the scene once for
// each sample with camera jitter and accumulates the results.
const offsetList = JitterVectors[ Math.max(0, Math.min(props.multiSample.sampleLevel, 5)) ];
const offsetList = JitterVectors[Math.max(0, Math.min(props.multiSample.sampleLevel, 5))];
const { x, y, width, height } = camera.viewport;
const baseSampleWeight = 1.0 / offsetList.length;
@@ -144,7 +148,7 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, sampleWeight);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
// compose rendered scene with compose target
composeTarget.bind();
@@ -186,7 +190,7 @@ export class MultiSamplePass {
//
// This manual approach to MSAA re-renders the scene once for
// each sample with camera jitter and accumulates the results.
const offsetList = JitterVectors[ Math.max(0, Math.min(props.multiSample.sampleLevel, 5)) ];
const offsetList = JitterVectors[Math.max(0, Math.min(props.multiSample.sampleLevel, 5))];
if (sampleIndex === -2 || sampleIndex >= offsetList.length) return -2;
@@ -194,7 +198,7 @@ export class MultiSamplePass {
const sampleWeight = 1.0 / offsetList.length;
if (sampleIndex === -1) {
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
ValueCell.update(compose.values.uWeight, 1.0);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
@@ -222,7 +226,7 @@ export class MultiSamplePass {
camera.update();
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
// compose rendered scene with compose target
composeTarget.bind();
@@ -240,7 +244,7 @@ export class MultiSamplePass {
compose.render();
sampleIndex += 1;
if (sampleIndex >= offsetList.length ) break;
if (sampleIndex >= offsetList.length) break;
}
}
@@ -274,33 +278,33 @@ export class MultiSamplePass {
const JitterVectors = [
[
[ 0, 0 ]
[0, 0]
],
[
[ 4, 4 ], [ -4, -4 ]
[4, 4], [-4, -4]
],
[
[ -2, -6 ], [ 6, -2 ], [ -6, 2 ], [ 2, 6 ]
[-2, -6], [6, -2], [-6, 2], [2, 6]
],
[
[ 1, -3 ], [ -1, 3 ], [ 5, 1 ], [ -3, -5 ],
[ -5, 5 ], [ -7, -1 ], [ 3, 7 ], [ 7, -7 ]
[1, -3], [-1, 3], [5, 1], [-3, -5],
[-5, 5], [-7, -1], [3, 7], [7, -7]
],
[
[ 1, 1 ], [ -1, -3 ], [ -3, 2 ], [ 4, -1 ],
[ -5, -2 ], [ 2, 5 ], [ 5, 3 ], [ 3, -5 ],
[ -2, 6 ], [ 0, -7 ], [ -4, -6 ], [ -6, 4 ],
[ -8, 0 ], [ 7, -4 ], [ 6, 7 ], [ -7, -8 ]
[1, 1], [-1, -3], [-3, 2], [4, -1],
[-5, -2], [2, 5], [5, 3], [3, -5],
[-2, 6], [0, -7], [-4, -6], [-6, 4],
[-8, 0], [7, -4], [6, 7], [-7, -8]
],
[
[ -4, -7 ], [ -7, -5 ], [ -3, -5 ], [ -5, -4 ],
[ -1, -4 ], [ -2, -2 ], [ -6, -1 ], [ -4, 0 ],
[ -7, 1 ], [ -1, 2 ], [ -6, 3 ], [ -3, 3 ],
[ -7, 6 ], [ -3, 6 ], [ -5, 7 ], [ -1, 7 ],
[ 5, -7 ], [ 1, -6 ], [ 6, -5 ], [ 4, -4 ],
[ 2, -3 ], [ 7, -2 ], [ 1, -1 ], [ 4, -1 ],
[ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ],
[ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ]
[-4, -7], [-7, -5], [-3, -5], [-5, -4],
[-1, -4], [-2, -2], [-6, -1], [-4, 0],
[-7, 1], [-1, 2], [-6, 3], [-3, 3],
[-7, 6], [-3, 6], [-5, 7], [-1, 7],
[5, -7], [1, -6], [6, -5], [4, -4],
[2, -3], [7, -2], [1, -1], [4, -1],
[2, 1], [6, 2], [0, 4], [4, 4],
[2, 5], [7, 5], [5, 6], [3, 7]
]
];

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,7 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { Vec3 } from '../../mol-math/linear-algebra';
import { spiral2d } from '../../mol-math/misc';
import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
import { Camera, ICamera } from '../camera';
import { StereoCamera } from '../camera/stereo';
@@ -88,6 +89,7 @@ export class PickPass {
this.groupPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'pickGroup');
// printTexture(this.webgl, this.groupPickTarget.texture, { id: 'group' })
this.depthPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'depth');
@@ -111,6 +113,8 @@ export class PickHelper {
private pickHeight: number
private halfPickWidth: number
private spiral: [number, number][]
private setupBuffers() {
const bufferSize = this.pickWidth * this.pickHeight * 4;
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
@@ -128,8 +132,8 @@ export class PickHelper {
this.pickX = Math.ceil(x * this.pickScale);
this.pickY = Math.ceil(y * this.pickScale);
const pickWidth = Math.ceil(width * this.pickScale);
const pickHeight = Math.ceil(height * this.pickScale);
const pickWidth = Math.floor(width * this.pickScale);
const pickHeight = Math.floor(height * this.pickScale);
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
this.pickWidth = pickWidth;
@@ -138,6 +142,8 @@ export class PickHelper {
this.setupBuffers();
}
this.spiral = spiral2d(Math.round(this.pickScale * this.pickPadding));
}
private syncBuffers() {
@@ -177,6 +183,7 @@ export class PickHelper {
renderer.setTransparentBackground(false);
renderer.setDrawingBufferSize(this.pickPass.objectPickTarget.getWidth(), this.pickPass.objectPickTarget.getHeight());
renderer.setPixelRatio(this.pickScale);
if (StereoCamera.is(camera)) {
renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight);
@@ -192,7 +199,7 @@ export class PickHelper {
this.dirty = false;
}
identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
private identifyInternal(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
const { webgl, pickScale } = this;
if (webgl.isContextLost) return;
@@ -251,7 +258,14 @@ export class PickHelper {
return { id: { objectId, instanceId, groupId }, position };
}
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport) {
identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
for (const d of this.spiral) {
const pickData = this.identifyInternal(x + d[0], y + d[1], camera);
if (pickData) return pickData;
}
}
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport, readonly pickPadding = 1) {
this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -13,7 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { DrawPass } from './draw';
@@ -70,6 +70,7 @@ const SsaoSchema = {
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
uTexSize: UniformSpec('v2'),
@@ -89,6 +90,7 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
@@ -118,6 +120,7 @@ const SsaoBlurSchema = {
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
};
@@ -139,6 +142,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
uNear: ValueCell.create(0.0),
uFar: ValueCell.create(10000.0),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
};
@@ -150,10 +154,10 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
}
function getBlurKernel(kernelSize: number): number[] {
let sigma = kernelSize / 3.0;
let halfKernelSize = Math.floor((kernelSize + 1) / 2);
const sigma = kernelSize / 3.0;
const halfKernelSize = Math.floor((kernelSize + 1) / 2);
let kernel = [];
const kernel = [];
for (let x = 0; x < halfKernelSize; x++) {
kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma)));
}
@@ -162,7 +166,7 @@ function getBlurKernel(kernelSize: number): number[] {
}
function getSamples(vectorSamples: Vec3[], nSamples: number): number[] {
let samples = [];
const samples = [];
for (let i = 0; i < nSamples; i++) {
let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
scale = 0.1 + scale * (1.0 - 0.1);
@@ -237,7 +241,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
export const PostprocessingParams = {
occlusion: PD.MappedStatic('on', {
on: PD.Group({
samples: PD.Numeric(32, {min: 1, max: 256, step: 1}),
samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
@@ -286,10 +290,14 @@ export class PostprocessingPass {
private readonly renderable: PostprocessingRenderable
private scale: number
private ssaoScale: number
private calcSsaoScale() {
// downscale ssao for high pixel-ratios
return Math.min(1, 1 / this.webgl.pixelRatio);
}
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
this.scale = 1 / this.webgl.pixelRatio;
this.ssaoScale = this.calcSsaoScale();
const { colorTarget, depthTexture } = drawPass;
const width = colorTarget.getWidth();
@@ -298,6 +306,7 @@ export class PostprocessingPass {
this.nSamples = 1;
this.blurKernelSize = 1;
// needs to be linear for anti-aliasing pass
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
@@ -305,7 +314,7 @@ export class PostprocessingPass {
this.randomHemisphereVector = [];
for (let i = 0; i < 256; i++) {
let v = Vec3();
const v = Vec3();
v[0] = Math.random() * 2.0 - 1.0;
v[1] = Math.random() * 2.0 - 1.0;
v[2] = Math.random();
@@ -317,14 +326,14 @@ export class PostprocessingPass {
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthBlurProxyTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
@@ -338,9 +347,13 @@ export class PostprocessingPass {
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const ssaoScale = this.calcSsaoScale();
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
this.ssaoScale = ssaoScale;
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.target.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.ssaoDepthTexture.define(sw, sh);
@@ -349,8 +362,8 @@ export class PostprocessingPass {
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
}
}
@@ -363,12 +376,26 @@ export class PostprocessingPass {
const outlinesEnabled = props.outline.name === 'on';
const occlusionEnabled = props.occlusion.name === 'on';
let invProjection = Mat4.identity();
const invProjection = Mat4.identity();
Mat4.invert(invProjection, camera.projection);
if (props.occlusion.name === 'on') {
ValueCell.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection);
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const b = this.ssaoRenderable.values.uBounds;
const v = camera.viewport;
const s = this.ssaoScale;
Vec4.set(b.ref.value,
Math.floor(v.x * s) / (w * s),
Math.floor(v.y * s) / (h * s),
Math.ceil((v.x + v.width) * s) / (w * s),
Math.ceil((v.y + v.height) * s) / (h * s)
);
ValueCell.update(b, b.ref.value);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
@@ -376,7 +403,9 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateSsaoBlur = true; }
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
needsUpdateSsaoBlur = true;
}
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
@@ -384,7 +413,7 @@ export class PostprocessingPass {
needsUpdateSsao = true;
this.nSamples = props.occlusion.params.samples;
ValueCell.updateIfChanged(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
}
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
@@ -394,10 +423,10 @@ export class PostprocessingPass {
needsUpdateSsaoBlur = true;
this.blurKernelSize = props.occlusion.params.blurKernelSize;
let kernel = getBlurKernel(this.blurKernelSize);
const kernel = getBlurKernel(this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
@@ -405,8 +434,12 @@ export class PostprocessingPass {
}
if (props.outline.name === 'on') {
const factor = Math.pow(1000, props.outline.params.threshold) / 1000;
const maxPossibleViewZDiff = factor * (camera.far - camera.near);
let { threshold } = props.outline.params;
// orthographic needs lower threshold
if (camera.state.mode === 'orthographic') threshold /= 5;
const factor = Math.pow(1000, threshold) / 1000;
// use radiusMax for stable outlines when zooming
const maxPossibleViewZDiff = factor * camera.state.radiusMax;
const outlineScale = props.outline.params.scale - 1;
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
@@ -414,7 +447,6 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
}
@@ -467,10 +499,10 @@ export class PostprocessingPass {
if (props.occlusion.name === 'on') {
const { x, y, width, height } = camera.viewport;
const sx = Math.floor(x * this.scale);
const sy = Math.floor(y * this.scale);
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const sx = Math.floor(x * this.ssaoScale);
const sy = Math.floor(y * this.ssaoScale);
const sw = Math.ceil(width * this.ssaoScale);
const sh = Math.ceil(height * this.ssaoScale);
this.webgl.gl.viewport(sx, sy, sw, sh);
this.webgl.gl.scissor(sx, sy, sw, sh);

View File

@@ -25,8 +25,8 @@ import { Viewport } from '../camera/util';
import { isDebugMode } from '../../mol-util/debug';
export const SmaaParams = {
edgeThreshold:PD.Numeric(0.1, { min: 0.05, max: 0.15, step: 0.01 }),
maxSearchSteps:PD.Numeric(16, { min: 0, max: 32, step: 1 }),
edgeThreshold: PD.Numeric(0.1, { min: 0.05, max: 0.15, step: 0.01 }),
maxSearchSteps: PD.Numeric(16, { min: 0, max: 32, step: 1 }),
};
export type SmaaProps = PD.Values<typeof SmaaParams>
@@ -44,7 +44,7 @@ export class SmaaPass {
}
constructor(private webgl: WebGLContext, input: Texture) {
if (typeof HTMLImageElement === undefined) {
if (typeof HTMLImageElement === 'undefined') {
if (isDebugMode) console.log(`Missing "HTMLImageElement" required for "SMAA"`);
return;
}

View File

@@ -12,7 +12,7 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
export function resizeCanvas(canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
let width = window.innerWidth;
let height = window.innerHeight;
if (container !== document.body) {

View File

@@ -100,7 +100,7 @@ describe('table', () => {
n: Column.ofArray({ array: ['row1', 'row2'], schema: Column.Schema.str }),
});
const s = { x: Column.Schema.int, y: Column.Schema.int };
const picked = Table.pickColumns(s, t, { y: Column.ofArray({ array: [3, 4], schema: Column.Schema.int })});
const picked = Table.pickColumns(s, t, { y: Column.ofArray({ array: [3, 4], schema: Column.Schema.int }) });
expect(picked._columns).toEqual(['x', 'y']);
expect(picked._rowCount).toEqual(2);
expect(picked.x.toArray()).toEqual([10, -1]);

View File

@@ -33,21 +33,21 @@ describe('linked list', () => {
expect(list.count).toBe(5);
});
it ('remove', () => {
it('remove', () => {
const list = create([1, 2, 3, 4]);
let fst = list.removeFirst();
const fst = list.removeFirst();
expect(fst).toBe(1);
expect(list.last!.value).toBe(4);
expect(list.count).toBe(3);
expect(toArray(list)).toEqual([2, 3, 4]);
let last = list.removeLast();
const last = list.removeLast();
expect(last).toBe(4);
expect(list.last!.value).toBe(3);
expect(list.count).toBe(2);
expect(toArray(list)).toEqual([2, 3]);
let n3 = list.find(3)!;
const n3 = list.find(3)!;
list.remove(n3);
expect(list.first!.value).toBe(2);
expect(list.last!.value).toBe(2);

View File

@@ -16,7 +16,7 @@ export const start = Tuple.fst;
export const end = Tuple.snd;
export const min = Tuple.fst;
export function max(i: Tuple) { return Tuple.snd(i) - 1; }
export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
export const size = Tuple.diff;
export const hashCode = Tuple.hashCode;
export const toString = Tuple.toString;

View File

@@ -19,7 +19,7 @@ export const ofBounds = I.ofBounds;
export function ofSortedArray(xs: Nums): OrderedSetImpl {
if (!xs.length) return Empty;
// check if the array is just a range
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return I.ofRange(xs[0], xs[xs.length - 1]);
if (S.isRange(xs)) return I.ofRange(xs[0], xs[xs.length - 1]);
return xs as any;
}

View File

@@ -22,9 +22,10 @@ export function ofRange(min: number, max: number) {
return ret;
}
export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); }
export function isRange(xs: Nums) { return xs[xs.length - 1] - xs[0] + 1 === xs.length; }
export function start(xs: Nums) { return xs[0]; }
export function end(xs: Nums) { return xs[xs.length - 1] + 1; }
export function end(xs: Nums) { return xs[xs.length - 1] + 1; }
export function min(xs: Nums) { return xs[0]; }
export function max(xs: Nums) { return xs[xs.length - 1]; }
export function size(xs: Nums) { return xs.length; }
@@ -59,9 +60,11 @@ export function getAt(xs: Nums, i: number) { return xs[i]; }
export function areEqual(a: Nums, b: Nums) {
if (a === b) return true;
const aSize = a.length;
let aSize = a.length;
if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false;
for (let i = 0; i < aSize; i++) {
if (isRange(a)) return true;
aSize--;
for (let i = 1; i < aSize; i++) {
if (a[i] !== b[i]) return false;
}
return true;
@@ -340,7 +343,7 @@ export function deduplicate(xs: Nums) {
}
export function indicesOf(a: Nums, b: Nums): Nums {
if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1));
if (areEqual(a, b)) return ofSortedArray(createRangeArray(0, a.length - 1));
const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
let i = sI, j = sJ;

View File

@@ -17,6 +17,7 @@ namespace SortedArray {
/** create sorted array [min, max) (it does NOT contain the max value) */
export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any;
export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
export const isRange: <T extends number = number>(array: ArrayLike<number>) => boolean = Impl.isRange as any;
export const has: <T extends number = number>(array: SortedArray<T>, x: T) => boolean = Impl.has as any;
/** Returns the index of `x` in `set` or -1 if not found. */

View File

@@ -15,7 +15,7 @@ interface IntTuple { '@type': 'int-tuple' }
namespace IntTuple {
export const Zero: IntTuple = 0 as any;
const { _int32, _float64, _int32_1, _float64_1 } = (function() {
const { _int32, _float64, _int32_1, _float64_1 } = (function () {
const data = new ArrayBuffer(8);
const data_1 = new ArrayBuffer(8);
return {
@@ -36,6 +36,12 @@ namespace IntTuple {
return _float64[0] as any;
}
/** snd - fst */
export function diff(t: IntTuple) {
_float64[0] = t as any;
return _int32[1] - _int32[0];
}
export function fst(t: IntTuple): number {
_float64[0] = t as any;
return _int32[0];

View File

@@ -8,14 +8,14 @@ import { ChunkedArray } from '../chunked-array';
describe('Chunked Array', () => {
it('creation', () => {
const arr = ChunkedArray.create<number, 2>(Array, 2, 2);
const arr = ChunkedArray.create<number, 2>(Array, 2, 2);
ChunkedArray.add2(arr, 1, 2);
ChunkedArray.add2(arr, 3, 4);
expect(ChunkedArray.compact(arr)).toEqual([1, 2, 3, 4]);
});
it('initial', () => {
const arr = ChunkedArray.create(Int32Array, 2, 6, new Int32Array([1, 2, 3, 4]));
const arr = ChunkedArray.create(Int32Array, 2, 6, new Int32Array([1, 2, 3, 4]));
ChunkedArray.add2(arr, 4, 3);
ChunkedArray.add2(arr, 2, 1);
ChunkedArray.add2(arr, 5, 6);
@@ -23,13 +23,13 @@ describe('Chunked Array', () => {
});
it('add many', () => {
const arr = ChunkedArray.create<number, 2>(Array, 2, 2);
const arr = ChunkedArray.create<number, 2>(Array, 2, 2);
ChunkedArray.addMany(arr, [1, 2, 3, 4]);
expect(ChunkedArray.compact(arr)).toEqual([1, 2, 3, 4]);
});
it('resize', () => {
const arr = ChunkedArray.create<number, 2>(Int32Array, 2, 2);
const arr = ChunkedArray.create<number, 2>(Int32Array, 2, 2);
ChunkedArray.add2(arr, 1, 2);
ChunkedArray.add2(arr, 3, 4);
ChunkedArray.add2(arr, 5, 6);
@@ -39,7 +39,7 @@ describe('Chunked Array', () => {
});
it('resize-fraction', () => {
const arr = ChunkedArray.create<number, 2>(Int32Array, 2, 2.5);
const arr = ChunkedArray.create<number, 2>(Int32Array, 2, 2.5);
ChunkedArray.add2(arr, 1, 2);
ChunkedArray.add2(arr, 3, 4);
ChunkedArray.add2(arr, 5, 6);

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