Compare commits

...

472 Commits

Author SHA1 Message Date
Alexander Rose
338489febd 0.3.7 2019-10-24 07:54:31 -07:00
David Sehnal
94e7820baf mol-data: SortedArray fix 2019-10-24 07:40:35 +02:00
Alexander Rose
a47df57709 more SortedArray.union tests (one failing) 2019-10-23 19:32:41 -07:00
Alexander Rose
f73d64f732 0.3.6 2019-10-23 18:15:46 -07:00
Alexander Rose
cfaef4c567 added more volume param docs 2019-10-23 18:14:16 -07:00
Alexander Rose
3ec2c6ded4 ui layout tweaks 2019-10-23 17:18:16 -07:00
Alexander Rose
2afaa35170 increased default wireframe thickness from 1 to 1.5 2019-10-23 17:17:34 -07:00
Alexander Rose
efcf4a77c6 add molecular surface wireframe visual 2019-10-23 17:16:40 -07:00
Alexander Rose
221e1de4e7 improved entity-source color theme 2019-10-23 12:25:59 -07:00
Alexander Rose
f2de2983c8 added isInteger helper 2019-10-23 12:25:38 -07:00
Alexander Rose
82c8499789 updated cif schemas 2019-10-23 11:43:19 -07:00
David Sehnal
d66ccdb255 mol-plugin: sequence view tweaks 2019-10-23 09:27:59 +02:00
David Sehnal
8d13c514b0 mol-data: SortedArray fix 2019-10-23 09:17:25 +02:00
Alexander Rose
f57aafba19 improved sequence numbers in sequence widget UI 2019-10-22 17:25:57 -07:00
Alexander Rose
0a7406db15 added more SortedArray.union tests, one failing 2019-10-22 15:17:18 -07:00
Alexander Rose
2f97e8b329 overset residue numbers in sequence ui 2019-10-22 12:24:53 -07:00
David Sehnal
22a8758852 mol-data: fixed SortedArray.union 2019-10-22 20:48:16 +02:00
Alexander Rose
bc6bc1d57a fix StructureElement.Stats 2019-10-22 10:22:02 -07:00
Alexander Rose
4a692b9a88 ensure applicable repr types are up-to-date 2019-10-22 10:20:55 -07:00
Alexander Rose
0094f800dc fixed entity-source color theme labels 2019-10-21 18:18:26 -07:00
Alexander Rose
9bd616f36f ensure file extension for image download 2019-10-21 17:59:09 -07:00
David Sehnal
4acc36628d mol-plugin: show sequence offsets in UI (wip) 2019-10-21 15:06:58 +02:00
David Sehnal
e550413778 mol-state: added createDefaultParams to StateAction and StateTransformer 2019-10-21 13:45:18 +02:00
David Sehnal
ccdc894979 ability to set default params for volume streaming 2019-10-21 13:39:05 +02:00
David Sehnal
322bc71d52 changed default color list to dark-2 to avoid a clash with EM volume map colors 2019-10-21 12:49:04 +02:00
Alexander Rose
7c55e4d234 0.3.5 2019-10-18 17:02:14 -07:00
Alexander Rose
8c8058290c fixed spacegroup index/number handling 2019-10-18 16:58:53 -07:00
David Sehnal
9d413bf0eb mol-util: palette fix missing valueLabel function 2019-10-18 15:29:45 +02:00
Alexander Rose
8fde5dd1bf 0.3.4 2019-10-17 11:47:10 -07:00
Alexander Rose
2f9ecb9396 package updates 2019-10-17 11:46:26 -07:00
Alexander Rose
85092279fa better handle unsupported extensions 2019-10-17 11:40:32 -07:00
Alexander Rose
d0189523e4 fix Loci stats for partial residue selections 2019-10-17 11:10:10 -07:00
Alexander Rose
a26d03205a tweak bindings to work better with one-button mouse 2019-10-16 18:26:22 -07:00
Alexander Rose
862eda6dd4 better min size limit in image ui 2019-10-16 17:54:46 -07:00
Alexander Rose
4a7f0fc206 hide non-applicable repr types in repr ui 2019-10-16 17:46:27 -07:00
Alexander Rose
6bbee58d39 guard against undefined values in ParameterControls 2019-10-16 16:42:14 -07:00
Alexander Rose
989faed410 added StructureSelectionQuery wrapping expr & query 2019-10-16 16:30:38 -07:00
Alexander Rose
198e2f2043 support for currentSelection in StructureSelectionHelper and surroundings query 2019-10-16 15:48:39 -07:00
Alexander Rose
6ae3d4110d added StructureElement.Loci.toStructure 2019-10-16 15:47:57 -07:00
David Sehnal
c34e1be43b Merge branch 'master' of https://github.com/molstar/molstar 2019-10-16 20:46:40 +02:00
David Sehnal
17a440ad9c moved StructureQuery.runExpr to Script.getStructureSelection to fix cyclic dep 2019-10-16 20:46:25 +02:00
Alexander Rose
04dcfc7adb check seqId compatibility for cyclic peptides 2019-10-16 11:01:07 -07:00
David Sehnal
ca66e334c1 mol-model: added StructureQuery.runExpr 2019-10-16 13:47:06 +02:00
David Sehnal
54bba4c92f mol-script: added internal.generator.current symbol 2019-10-16 13:38:58 +02:00
David Sehnal
9c046b808c mol-script: added Bundle support 2019-10-16 13:26:05 +02:00
David Sehnal
ca5d1865cc mol-model: Loci fixes 2019-10-16 12:48:21 +02:00
Alexander Rose
d8f1aa5bc9 support for streaming multiple em volumes, limit to associate maps 2019-10-15 17:38:42 -07:00
David Sehnal
6ac790e083 mol-model: renamed StructureSelection.toLoci* 2019-10-15 08:49:06 +02:00
David Sehnal
d0870e4bbb added highlight example to basic wrapper, better empty loci handling 2019-10-15 08:44:50 +02:00
Alexander Rose
5138595f36 added more StructureSelectionQueries 2019-10-14 16:26:54 -07:00
Alexander Rose
d9aa5684a9 show non-polymer residues of polymer entities as ball&stick 2019-10-14 14:09:34 -07:00
Alexander Rose
8e2521a7a9 consider coarse/non-coarse backbone when checking distance for backbone links 2019-10-14 14:09:12 -07:00
Alexander Rose
7dd7a117cb wip, terminal gaps 2019-10-11 16:55:39 -07:00
Alexander Rose
defbadf4d7 handle polymer ends in visuals properly 2019-10-11 16:55:22 -07:00
Alexander Rose
131e88080a 0.3.3 2019-10-10 10:43:52 -07:00
Alexander Rose
fb78b886c1 collapse link labels to hide repeated ids 2019-10-10 10:41:56 -07:00
Alexander Rose
aed0b87b16 fix fractional canvas/image dimensions 2019-10-10 10:35:44 -07:00
Alexander Rose
1316cc6a40 fix StructureSelectionControls.focus for single element 2019-10-09 17:52:59 -07:00
Alexander Rose
944d370c14 0.3.2 2019-10-09 16:59:24 -07:00
Alexander Rose
74f9aa6af6 package updates 2019-10-09 16:55:44 -07:00
Alexander Rose
c20c9c9917 fixed setting CollapsableControls default state 2019-10-09 16:54:15 -07:00
Alexander Rose
4801435d72 add controls to create image 2019-10-09 16:10:36 -07:00
Alexander Rose
33fd105ef7 add ImagePass 2019-10-09 16:09:43 -07:00
Alexander Rose
3ea3fb8984 support rendering with transparent background 2019-10-09 16:08:48 -07:00
Alexander Rose
b4bbc544ca 0.3.1 2019-10-07 17:27:35 -07:00
Alexander Rose
5f880e920b add focus button to StructureSelectionControls 2019-10-07 17:26:16 -07:00
Alexander Rose
bcce801dd7 ensure sequence markers are up-to-date 2019-10-07 16:10:21 -07:00
Alexander Rose
00f9dcee4a added StructureSymmetryMatesFromModel transform, fixes for findMatesRadius 2019-10-07 15:31:54 -07:00
Alexander Rose
505af2bc96 fix help for scrollFocus 2019-10-07 12:04:20 -07:00
Alexander Rose
c217aab5fc reduced default unicell cage thickness 2019-10-07 11:23:50 -07:00
Alexander Rose
5d5fd0028f added operator name & hkl color themes 2019-10-07 11:20:10 -07:00
Alexander Rose
c88693dfdd improved color theme legend with labels from data 2019-10-07 11:18:08 -07:00
David Sehnal
0a16ec1bd2 mol-plugin: added TextInput wrapper and RGB color input 2019-10-05 14:52:00 +02:00
Alexander Rose
6f36a3c724 0.3.0 2019-10-04 17:21:16 -07:00
Alexander Rose
71496bd1ef package updates 2019-10-04 17:20:38 -07:00
Alexander Rose
c5a99a7c12 improved lociApply for whole structures 2019-10-04 17:07:08 -07:00
Alexander Rose
cfaf33d696 also look for 4 and 7 member sugar rings 2019-10-04 15:55:47 -07:00
Alexander Rose
e24c76d2bf update modifierkeys on mouse input 2019-10-04 15:40:14 -07:00
Alexander Rose
b6273205a2 removed duplicate type 2019-10-04 15:18:33 -07:00
Alexander Rose
b38193aa19 for unknown carbs draw polygonal prisms 2019-10-04 15:13:00 -07:00
Alexander Rose
f9cfacae23 sequence widget, bind onMouseLeave 2019-10-04 12:26:05 -07:00
Alexander Rose
ac46317dc6 fix lighting for orthographic projection close to camera 2019-10-04 11:53:38 -07:00
Alexander Rose
2b492a5a61 lighter monospace font 2019-10-04 10:44:08 -07:00
Alexander Rose
a2133657f0 chnaged label Unit->Chain in sequence widget 2019-10-04 10:42:07 -07:00
David Sehnal
e8de45789f mol-plugin: optimized sequence control 2019-10-04 16:52:36 +02:00
David Sehnal
3d2bd167ca mol-plugin: use monospace font for sequence 2019-10-04 15:34:59 +02:00
David Sehnal
504c8626dc mol-plugin: fixed PluginCommands.State.Highlight (wip) 2019-10-04 15:22:26 +02:00
Alexander Rose
f4b29dc7e0 ui, offset expanded color param 2019-10-03 17:24:24 -07:00
Alexander Rose
b34c5c743b tweaked ligandPlusConnected selection 2019-10-03 16:40:44 -07:00
Alexander Rose
c57311d6c0 carbohydrate improvements, updated carb table 2019-10-03 16:04:32 -07:00
Alexander Rose
4d786dc697 fix missing awaits in StructureRepresentationHelper.preset 2019-10-03 11:50:18 -07:00
Alexander Rose
062e3e055a show full compId for modified residues in sequence widget 2019-10-03 10:05:31 -07:00
Alexander Rose
1465174a45 Merge branch 'master' of https://github.com/molstar/molstar 2019-10-02 17:57:38 -07:00
Alexander Rose
cc00ada5a3 add model Unitcell representation 2019-10-02 17:57:19 -07:00
Alexander Rose
cbf312b62d calculate symmetry operators for transform around deposited coordinates 2019-10-02 17:56:41 -07:00
Alexander Rose
2be3144086 add .volume to SpacegroupCell 2019-10-02 17:55:03 -07:00
Alexander Rose
da3acd9d19 calculate model center as dynamic prop 2019-10-02 17:53:51 -07:00
Alexander Rose
34b048479b added arrayMinMax util 2019-10-02 17:53:03 -07:00
Alexander Rose
ca92931bf2 mol-geo improvements for mesh, cage and primitive 2019-10-02 17:52:48 -07:00
David Sehnal
0d3daeb823 mol-plugin: semi-transparent viewport controls background 2019-10-02 13:11:50 +02:00
David Sehnal
58b1d7e0eb mol-model & formats: added fallback when chem_comp category is missing 2019-10-02 12:46:23 +02:00
Alexander Rose
c5997ed056 reset buttons and modifier keys state when browser window looses focus 2019-10-01 14:11:01 -07:00
Alexander Rose
da1deee7f3 switched off marker interpolation for webgl2 2019-10-01 10:58:26 -07:00
Alexander Rose
a4eaff3175 add background to canvas icons for legibility with different render background colors 2019-10-01 10:30:44 -07:00
Alexander Rose
211cfc0bd3 fix highlight persisting after rotation 2019-10-01 09:52:18 -07:00
David Sehnal
c0f85b691d mol-plugin: reverted msp-layout-region background !important, because it broke the visual style 2019-10-01 17:42:42 +02:00
David Sehnal
6b93d58ea6 mol-plugin: updated color select control 2019-10-01 17:33:49 +02:00
Alexander Rose
5bce423b49 fully mark carbohydrate terminal links from StructureElement.Loci 2019-09-30 17:38:05 -07:00
Alexander Rose
01b0dde503 set plugin Context.customState: unknown 2019-09-30 16:38:55 -07:00
Alexander Rose
ed1bc8cb7d ensure single element loci have some volume in volume interaction behavior 2019-09-30 15:52:55 -07:00
Alexander Rose
abe559261b show insertion code in labels 2019-09-30 14:36:42 -07:00
Alexander Rose
b0cdf22cb8 various interaction behaviors improvements 2019-09-30 14:26:59 -07:00
Alexander Rose
a61ba71f1e handle mixed sizes in link visuals 2019-09-30 12:11:07 -07:00
Alexander Rose
7061d57559 tweak some bond distance thresholds 2019-09-30 11:06:49 -07:00
Alexander Rose
83a1e6c87c improved entity subtype assignment (use chem comp type) 2019-09-30 11:06:19 -07:00
Alexander Rose
c18888b8de 0.2.15 2019-09-27 17:19:57 -07:00
Alexander Rose
2d80935e00 ignore non-identity 'given' NCS operators 2019-09-27 17:18:14 -07:00
Alexander Rose
4287d158b6 add Mat3.isIdentity 2019-09-27 17:18:06 -07:00
Alexander Rose
4e9b569178 show logo in empty viewer 2019-09-27 16:48:10 -07:00
Alexander Rose
5d626d291b added viewer favicon 2019-09-27 16:48:00 -07:00
Alexander Rose
afa4a01c44 package updates 2019-09-27 16:41:53 -07:00
Alexander Rose
3c4a23c5a3 ensure msp-layout-region background 2019-09-27 15:22:27 -07:00
Alexander Rose
8d92c976d9 css 'select' tweaks 2019-09-27 13:19:01 -07:00
Alexander Rose
1a9adfad29 0.2.14 2019-09-27 11:17:53 -07:00
Alexander Rose
a9e9a5974d fix volumeserver cmd arg types 2019-09-27 11:10:11 -07:00
Alexander Rose
27160aa8fe fix stale array use 2019-09-26 15:33:52 -07:00
David Sehnal
6a71af00cf mol-plugin: Toast support 2019-09-26 16:18:57 +02:00
David Sehnal
6c0938db50 mol-plugin: added InitVolumeStreaming binding param 2019-09-26 15:11:13 +02:00
Alexander Rose
b76173c82f 0.2.13 2019-09-25 17:25:25 -07:00
Alexander Rose
0bcf2b1ff4 fix wrong style.overflow assignment 2019-09-25 16:07:25 -07:00
Alexander Rose
d074415a26 added more param docs 2019-09-25 11:55:00 -07:00
Alexander Rose
42f3e38026 0.2.12 2019-09-24 14:21:58 -07:00
Alexander Rose
74c16ee7ba package updates 2019-09-24 14:19:44 -07:00
Alexander Rose
f6ef22b917 added link cylinder to ellopsoids repr 2019-09-24 14:16:19 -07:00
Alexander Rose
c3f3d7efda check if atomistic before query modified 2019-09-24 12:20:38 -07:00
Alexander Rose
61d44efdc4 fix typo 2019-09-24 12:12:19 -07:00
Alexander Rose
ad200c86ec Merge branch 'master' of https://github.com/molstar/molstar 2019-09-24 12:10:54 -07:00
Alexander Rose
4aecf4e0b4 added ElementSequenceWrapper & ChainSequenceWrapper 2019-09-24 12:09:54 -07:00
Alexander Rose
d81f37c78b added AtomicUnit.residueCount 2019-09-24 12:07:26 -07:00
Alexander Rose
c2979ce5ab StructureSequence, handle empty coarse hierarchies 2019-09-24 12:06:55 -07:00
Alexander Rose
09c7edce88 smaller terminal links and dashed terminal links to metals 2019-09-24 10:11:42 -07:00
David Sehnal
beefb79258 mol-plugin: better default Structure Complex, extended StructureComplexElement transform 2019-09-24 15:17:05 +02:00
David Sehnal
529c6ac81c mol-plugin: fix SelectionFromScript & volume streaming bugs 2019-09-24 14:07:42 +02:00
Alexander Rose
84b988ea96 fix csv parser choking on newline at end of file 2019-09-23 16:46:35 -07:00
Alexander Rose
35e978efc9 added Table.toArrays 2019-09-23 16:45:58 -07:00
David Sehnal
19559d01f7 mol-state: call canAutoUpdate with correct parameters 2019-09-23 16:53:31 +02:00
David Sehnal
56cec343e2 Proteopedia wrapper fix 2019-09-23 16:12:40 +02:00
David Sehnal
20c9bd0130 mol-plugin: CSS fix 2019-09-23 15:56:48 +02:00
David Sehnal
1336997c58 mol-plugin: volume streaming support for LinkLoci, update "current box" when switching to surroundings, init behavior fix 2019-09-23 15:47:55 +02:00
David Sehnal
b178fdefdc mol-plugin: Camera focus duration default value fix 2019-09-23 14:59:44 +02:00
Alexander Rose
453d60060a ui, print length of sequences that are too long 2019-09-22 19:48:59 -07:00
Alexander Rose
901fac97a0 basic support for .3dg files 2019-09-22 19:44:37 -07:00
Alexander Rose
29b6a88343 check for Performance object capabilities 2019-09-22 19:43:13 -07:00
Alexander Rose
a2217c7fc6 fix csv parser chunking 2019-09-22 19:42:31 -07:00
Alexander Rose
3eec30aa42 sequence improvements, create sequence from coarse elements 2019-09-22 18:29:46 -07:00
Alexander Rose
f352e19e90 config for debugging 2019-09-22 09:32:19 -07:00
Alexander Rose
be65ef89bc 0.2.11 2019-09-20 17:23:25 -07:00
Alexander Rose
cfc46073c0 package updates 2019-09-20 17:22:39 -07:00
Alexander Rose
1819a2bbda add wwpdb provider for emdb contourLevel 2019-09-20 17:14:53 -07:00
Alexander Rose
aa3a42f94e added simple xml parser 2019-09-20 17:13:13 -07:00
Alexander Rose
4bff55b612 improve theme applicability checks 2019-09-20 15:08:55 -07:00
Alexander Rose
dbc4e09909 add 'disorder' to uncertainty theme label/description 2019-09-20 14:46:46 -07:00
Alexander Rose
0a074c8a66 add modelNum to location labels 2019-09-20 11:10:40 -07:00
Alexander Rose
8b314ebb75 add border to .msp-layout-standard 2019-09-20 10:20:12 -07:00
Alexander Rose
4dc0791aeb tweaked slider rail/handle color 2019-09-20 10:06:59 -07:00
Alexander Rose
e3483f11b1 adjusted maxCovalentHydrogenBondingLength 2019-09-20 09:32:45 -07:00
Alexander Rose
244a678ab7 fix Props.residue.label_comp_id for micro het 2019-09-19 19:11:57 -07:00
Alexander Rose
8e1d44fabc fixes, seq wrapper mark every loci, binding docs & cleanup 2019-09-19 18:33:44 -07:00
Alexander Rose
954a5b58e0 tweaked structure and volume surroundings behaviors 2019-09-19 18:10:07 -07:00
Alexander Rose
53223bc72b support EveryLoci in StructureRepresentation.mark 2019-09-19 17:10:28 -07:00
Alexander Rose
a11a1fa07e formating 2019-09-19 17:09:26 -07:00
Alexander Rose
9ab4001544 get Behavior defaultParams from transformer params 2019-09-19 17:09:15 -07:00
Alexander Rose
3e3b71d230 added Interval.ofLength 2019-09-19 17:08:36 -07:00
Alexander Rose
186929269b refactored bindings and interactivity 2019-09-19 17:08:20 -07:00
Alexander Rose
afa7a04af0 various ui tweaks, added CollapsableControls 2019-09-19 17:03:06 -07:00
Alexander Rose
2861d12f04 inline help for params, color theme legend ui 2019-09-16 16:47:58 -07:00
Alexander Rose
65e1212b2f wip, focusZoom action 2019-09-13 16:30:55 -07:00
Alexander Rose
47136c8b71 wip, focus input bindings 2019-09-13 12:57:46 -07:00
Alexander Rose
375b829562 wip, simplified camera class 2019-09-13 10:53:56 -07:00
Alexander Rose
2718d42b01 wip, input bindings, radius-based clipping 2019-09-12 16:47:19 -07:00
Alexander Rose
93d4118c0a Merge branch 'master' of https://github.com/molstar/molstar 2019-09-12 09:37:31 -07:00
David Sehnal
3a2a47af12 removed unused comments 2019-09-12 16:09:47 +02:00
David Sehnal
0eacdfca85 added transition duration effect to camera focus 2019-09-12 16:07:48 +02:00
David Sehnal
a7388be25f mol-canvas3d: fixed camera focus 2019-09-12 15:34:27 +02:00
Alexander Rose
5fcdcb1275 webpack.config cleanup, package updates 2019-09-11 17:57:54 -07:00
Alexander Rose
4635bdffb0 0.2.10 2019-09-11 16:11:33 -07:00
Alexander Rose
72b1c36111 basic microheterogeneity support 2019-09-11 15:50:57 -07:00
Alexander Rose
fc5ff601a8 typed generic (Column<T>) return of windowColumn 2019-09-11 15:49:13 -07:00
Alexander Rose
8dd142fc9f fixed ellipsoids with three identical eigenvalues (render sphere) 2019-09-11 10:35:14 -07:00
Alexander Rose
97f24293e2 0.2.9 2019-09-10 16:52:30 -07:00
Alexander Rose
f4beba5215 pdb-parser, aniso records 2019-09-10 16:49:10 -07:00
Alexander Rose
e2abe0f52a pdb-parser, atom-site fixes 2019-09-10 16:48:36 -07:00
Alexander Rose
5d18643374 pdb-parser, move atom-site methods to separate file 2019-09-10 15:50:04 -07:00
Alexander Rose
769f2a30c2 package updates 2019-09-10 15:43:00 -07:00
Alexander Rose
0e040d7744 added occupancy color theme 2019-09-10 15:31:57 -07:00
Alexander Rose
7600d0a44e add ellipsoid repr showing mmcif thermal displacement 2019-09-10 15:10:58 -07:00
Alexander Rose
9113d6d189 only show applicable repr types in ui 2019-09-10 15:10:13 -07:00
Alexander Rose
fd9dac86b9 combined duplicate chem_comp_bond props 2019-09-10 15:09:33 -07:00
Alexander Rose
fe1f3bd4bb use structure.uniqueResidues, removed duplicated code 2019-09-10 11:12:00 -07:00
Alexander Rose
d950718110 linalg: return out array for .toArray, create symmetric mat3 2019-09-10 10:50:34 -07:00
Alexander Rose
4be903b9d5 added atom_site_anisotrop to mmcif schema 2019-09-09 17:35:16 -07:00
Alexander Rose
8050644869 mat3 symmetricEigenvalues and eigenvectors 2019-09-09 17:32:45 -07:00
Alexander Rose
347c1986df require output object for Tensor.to* methods 2019-09-09 17:32:02 -07:00
David Sehnal
eac4a8988b mol-model: added Model.entryId; mol-plugin: toggle validation tooltip; fixed using correct entryId for various custom properties 2019-09-05 16:09:05 +02:00
Alexander Rose
25137f29d2 typescript 3.6 compat fixes 2019-09-04 17:26:42 -07:00
Alexander Rose
4601fe74bb packaage updates 2019-09-04 17:26:21 -07:00
Alexander Rose
f0b54e9cbf 0.2.8 2019-09-04 12:18:43 -07:00
Alexander Rose
fdb45c3624 improved canvas3d.clear 2019-09-04 12:17:22 -07:00
Alexander Rose
f3b1ec0ed8 Merge branch 'master' of https://github.com/molstar/molstar 2019-09-04 12:05:42 -07:00
Alexander Rose
79510614b9 add .uniqueElementCount and .polymerUnitCount to Structure 2019-09-04 12:04:58 -07:00
Alexander Rose
f8c9bc6812 add volume-server npm start script 2019-09-04 12:03:44 -07:00
Alexander Rose
85d35aab90 handle special CCP4 spacegroup numbers 2019-09-04 12:03:14 -07:00
David Sehnal
f5b09dbd10 mol-plugin: added customState to PluginContext 2019-09-03 14:42:59 +02:00
Alexander Rose
57ea322fd6 cellpack loader tweak 2019-08-30 19:09:53 -07:00
Alexander Rose
c31aab5594 wip, cellpack loader 2019-08-30 18:56:30 -07:00
Alexander Rose
a5556e8c41 imroved sequence widget entity dropdown 2019-08-30 18:55:46 -07:00
Alexander Rose
0ce2966c47 lazy calculation of Structure.polymerResidueCount 2019-08-30 18:54:54 -07:00
Alexander Rose
f2c04a13af adjust grid-cell-count in partitionAtomicUnitByResidue 2019-08-30 17:28:33 -07:00
Alexander Rose
60ba0de219 CifField creation optimizations 2019-08-30 17:27:58 -07:00
Alexander Rose
99e515604e skip identical positions in spheres and lines bounding sphere calculation 2019-08-30 17:26:52 -07:00
Alexander Rose
84dfa60fc2 added CifCategory.ofTable and 'list' valueType support for CifField.ofColumn 2019-08-29 17:42:30 -07:00
Alexander Rose
a753095f92 added ImportString and ImportJson transforms 2019-08-29 17:31:44 -07:00
David Sehnal
ce0ff2fed8 mol-model: optionally compute centroid based bounding sphere (disabled for now) 2019-08-29 15:46:47 +02:00
Alexander Rose
1c1deb5ee7 0.2.7 2019-08-28 17:48:22 -07:00
Alexander Rose
e28749b794 overpaint improvements, merge and filter 2019-08-28 17:34:41 -07:00
Alexander Rose
7566cc89ca added transperancy-from-bundle, improved overpaint 2019-08-28 15:29:17 -07:00
Alexander Rose
8b76c09559 added Script namespace with helpers 2019-08-28 15:28:35 -07:00
Alexander Rose
c60d7d3faf removed console statements 2019-08-28 12:49:33 -07:00
Alexander Rose
3eb3d1e27e re-apply select-mark to all representation of an updated structure 2019-08-28 12:43:23 -07:00
Alexander Rose
3083c7d9e0 overpaint improvements 2019-08-28 12:04:47 -07:00
Alexander Rose
9184219976 StructureElement loci & bundle fixes 2019-08-28 12:03:26 -07:00
Alexander Rose
90a3d302f3 loci helper methods, isEmpty 2019-08-28 12:00:39 -07:00
Alexander Rose
02dd5f9d11 fix, set new source in StructureRepresentation3D.update 2019-08-27 17:05:10 -07:00
Alexander Rose
3685c92b52 add missing info in ObjectList param 2019-08-27 17:04:23 -07:00
Alexander Rose
e1a04c8b0b renamed StructureSelection-related transforms 2019-08-27 14:51:10 -07:00
Alexander Rose
5a704b7974 renamed StructureElement.Query to StructureElement.Bundle 2019-08-27 14:29:10 -07:00
Alexander Rose
2376d0a9c6 split up StructureElement 2019-08-27 13:54:00 -07:00
Alexander Rose
87e0c05ec0 tweaked cellpack loader: baseUrl, binary membrane file 2019-08-27 09:54:27 -07:00
Alexander Rose
8140b75086 fixed polymer/chain-id color theme for coarse models 2019-08-27 09:53:38 -07:00
Alexander Rose
1f32d5469e ignoreHydrogens in cellpack presets 2019-08-26 17:30:53 -07:00
Alexander Rose
b09549439f added StructureElement.Stats and improved loci-labels 2019-08-26 17:14:32 -07:00
Alexander Rose
e1b7a5b267 wip, structure tools, repr presets 2019-08-26 17:13:28 -07:00
Alexander Rose
1ed420aeb7 added entity.subtype based on _entity_poly.type and _pdbx_entity_branch.type 2019-08-26 17:10:12 -07:00
Alexander Rose
301a23b1e0 refactored cellpack curve to resuse Vec3 objects 2019-08-25 21:42:07 -07:00
Alexander Rose
e53bb51f15 removed superfluous gl.viewport call 2019-08-25 21:05:18 -07:00
Alexander Rose
4e6e8fed82 add rna in hiv cellpack models 2019-08-25 21:04:40 -07:00
Alexander Rose
b2809ad631 handle curves/fibers in mesoscale models 2019-08-24 16:32:49 -07:00
Alexander Rose
37648b41ea added uInteriorDarkening factor to renderer 2019-08-23 22:28:27 -07:00
Alexander Rose
fac183c2ee allow partial reprParams in StructureRepresentation3DHelpers.createParams 2019-08-23 22:27:20 -07:00
Alexander Rose
b113448e01 added 'serve' npm script 2019-08-23 22:26:25 -07:00
Alexander Rose
c585ba7791 color theme and list tweaks, added turbo 2019-08-23 12:23:56 -07:00
Alexander Rose
afb6c65c48 fix sequence widget update on state changes 2019-08-23 12:23:15 -07:00
Alexander Rose
527e91c9eb fix Structure.unitIndexMap creation when units are unsorted 2019-08-23 12:22:15 -07:00
Alexander Rose
9e2ef87611 don't use Mesh.uniformTriangleGroup when WebGL2 is available 2019-08-22 16:17:22 -07:00
Alexander Rose
f2342c3273 added structure gaussian surface complex-visual 2019-08-22 12:07:07 -07:00
Alexander Rose
347981792e added Structure.serialMapping 2019-08-22 12:04:05 -07:00
Alexander Rose
032d2f2784 removed fillUniform, use Array.fill 2019-08-22 08:44:32 -07:00
Alexander Rose
ddc1119a80 0.2.6 2019-08-21 13:38:22 -07:00
Alexander Rose
0fe6774f04 fixed comparison and typo 2019-08-21 13:37:04 -07:00
Alexander Rose
2a71e44ae8 updated packages 2019-08-21 13:36:37 -07:00
Alexander Rose
1257f1ce85 label tweak 2019-08-21 12:17:05 -07:00
Alexander Rose
07cd9f4b16 css fix 2019-08-21 12:09:19 -07:00
Alexander Rose
ed5ff1c9ce slider ui tweaks 2019-08-21 10:13:42 -07:00
Alexander Rose
7ffc2db76e fix trackball controls dropping inputs 2019-08-21 09:42:30 -07:00
Alexander Rose
03067ca6d6 wip, structure tools quality option 2019-08-20 16:39:32 -07:00
Alexander Rose
a69f1337d7 color refactoring 2019-08-20 12:51:27 -07:00
Alexander Rose
5e83c3350a added Structure.root 2019-08-20 12:51:16 -07:00
Alexander Rose
f051d2d01e use model.entry in volume-streaming 2019-08-20 12:48:59 -07:00
Alexander Rose
257340283b wip, color list refactoring 2019-08-20 09:53:34 -07:00
Alexander Rose
e769d77ec8 wip, color theme refactoring 2019-08-19 18:50:46 -07:00
Alexander Rose
1923535918 limit max number of generated colors 2019-08-19 12:44:34 -07:00
Alexander Rose
dd3fc5620b label fix 2019-08-19 12:43:52 -07:00
Alexander Rose
7165258431 sequence widget fixes 2019-08-19 12:43:31 -07:00
Alexander Rose
df9b367e0b tweaked structure labeling 2019-08-19 12:43:08 -07:00
Alexander Rose
ed1ae71f71 optimized repr loci iteration, marking and extendToWholeChains 2019-08-18 13:01:31 -07:00
Alexander Rose
1c58bca454 fix wrong props object in Structure creation 2019-08-18 12:58:01 -07:00
Alexander Rose
c01be0644e improved handling of polymer trace termini 2019-08-16 20:01:20 -07:00
Alexander Rose
66b9f6104c improved low-poly geo (cylinder, sheet, tube, ribbon) 2019-08-16 17:14:57 -07:00
Alexander Rose
af4d2c4003 Merge branch 'master' of https://github.com/molstar/molstar 2019-08-15 17:18:02 -07:00
Alexander Rose
3b1a2f19a4 wip, structure tools, repr presets 2019-08-15 17:15:50 -07:00
Alexander Rose
6b874786a8 only extract CrossLinkRestraints when needed 2019-08-15 16:07:58 -07:00
Alexander Rose
f9d2560468 fixed symmetry operator when NCS operators are present 2019-08-15 16:07:18 -07:00
David Sehnal
961034584a model-server: config can be (partially) provided as a separate JSON file 2019-08-15 17:22:23 +02:00
David Sehnal
f6d11a59a3 model-server: wip config refactoring 2019-08-15 17:11:55 +02:00
Alexander Rose
53ee758378 wip, sequence widget 2019-08-14 17:21:53 -07:00
Alexander Rose
c80c630810 param control tweaks 2019-08-14 17:21:40 -07:00
Alexander Rose
13cd6e82ba added StructureFromTrajectory, improved multi-model structures 2019-08-14 17:20:40 -07:00
David Sehnal
f2966032d9 model-server: rest api, swagger ui, response schemas, bug fixes 2019-08-14 15:04:07 +02:00
David Sehnal
76503b52f5 wip model-server 2019-08-14 14:21:13 +02:00
Alexander Rose
aa24be8e9b type fixes 2019-08-13 16:44:19 -07:00
Alexander Rose
5d7bb894d4 wip, structure tools 2019-08-13 16:18:44 -07:00
Alexander Rose
fcf559fa6b toggle hydrogens in structure view tools 2019-08-13 15:55:32 -07:00
Alexander Rose
27963b5aed added option to ignore hydrogens in structure representations 2019-08-13 14:54:38 -07:00
Alexander Rose
40d539c4aa viewport ui improvements: mouse handling, config 2019-08-13 11:42:42 -07:00
David Sehnal
d1433aaf7b mol-state: added StateTree.subtreeHasRef 2019-08-13 17:19:36 +02:00
Alexander Rose
8f4bf9a314 added outsideControls to PluginLayoutStateParams 2019-08-12 17:30:27 -07:00
Alexander Rose
cca0f407f6 improved StructureRepresentationHelper 2019-08-12 16:47:09 -07:00
Alexander Rose
1e9e41754a added isApplicable support to representation provider & registry 2019-08-12 16:46:48 -07:00
David Sehnal
38dc2d6e26 mol-script: macro support 2019-08-12 16:00:53 +02:00
David Sehnal
ea46b1c8c8 mol-script: bond* => link*; make link tests "covalent" by default; wip link tests 2019-08-12 15:29:50 +02:00
Alexander Rose
5846d7c4b4 wip, structure view tools 2019-08-09 18:40:07 -07:00
Alexander Rose
214176ce2e handle incompatibility of StructureElement.Query and Structure 2019-08-09 18:18:32 -07:00
Alexander Rose
ee4cb214e1 updated cif schemas 2019-08-09 18:16:58 -07:00
Alexander Rose
01cca9e8a6 use .componentDidUpdate in SliderBase 2019-08-09 11:08:53 -07:00
Alexander Rose
7a23493e19 package updates 2019-08-09 10:26:41 -07:00
Alexander Rose
e109f069a8 wip, structure ui tools 2019-08-08 17:46:23 -07:00
Alexander Rose
82e667e402 added chemCompType and other mol-script improvements 2019-08-08 17:16:47 -07:00
Alexander Rose
c28feb2d1c added objectPrimitive to unit and mol-script 2019-08-08 13:56:21 -07:00
Alexander Rose
8447a2d4f2 add missing alias for cbrt 2019-08-08 13:54:20 -07:00
Alexander Rose
f9ebf1c399 better renderer defaults (occlusian radius, cmaera dist clipping) 2019-08-08 13:53:49 -07:00
Alexander Rose
286b27720a StructureElement fixes 2019-08-07 17:04:10 -07:00
Alexander Rose
dc0e54c275 Merge branch 'master' of https://github.com/molstar/molstar 2019-08-07 08:37:26 -07:00
David Sehnal
b710291d5e mol-plugin: animation controls tweak 2019-08-07 12:15:36 +02:00
David Sehnal
d84d5f38f5 mol-model: implemented includeConnected query 2019-08-07 12:10:24 +02:00
Alexander Rose
9bea13438f improved picking pass 2019-08-06 12:50:45 -07:00
Alexander Rose
2fc28f6005 improved StructureElement.Query 2019-08-06 11:16:50 -07:00
Alexander Rose
bb07d6ec56 imroved download helper and state download 2019-08-06 10:02:49 -07:00
Alexander Rose
3cdfd04048 improved element loci remap, added SortedArray.indexOfInRange 2019-08-06 08:38:36 -07:00
Alexander Rose
2120a258f9 remap loci per repr not per visual 2019-08-06 08:30:21 -07:00
Alexander Rose
2b5e49d215 use LociStructureSelection in plugin structure tools 2019-08-05 15:09:16 -07:00
Alexander Rose
b88bf9bdf2 LociStructureSelection based on StructureElement.Query 2019-08-05 15:08:43 -07:00
Alexander Rose
df0f15d132 added StructureElement.Query for Loci serialization 2019-08-05 15:08:36 -07:00
Alexander Rose
a6319bfb3d tweakes to avoid cyclic import issues 2019-08-05 12:32:01 -07:00
Alexander Rose
d6278cb3eb sorted-ranges, .areEqual and .forEach 2019-08-05 12:30:53 -07:00
Alexander Rose
560da38687 fixed exceptBy and structureSubtract 2019-08-03 14:55:55 -07:00
Alexander Rose
83ba9d8776 avoid .apply for long arg lists 2019-08-02 21:53:59 -07:00
Alexander Rose
ee776e6e3e wip, structure tools 2019-08-02 17:18:07 -07:00
Alexander Rose
3e52496b4e sequence widget, chain -> unit 2019-08-02 17:17:21 -07:00
Alexander Rose
2659b96008 added math.cbrt to mol-script 2019-08-02 17:16:47 -07:00
Alexander Rose
a8be84701b improved screendoor transparency with multi-sample 2019-08-02 10:23:16 -07:00
Alexander Rose
0c79aa1709 show number of selected elements and structures 2019-08-01 16:09:33 -07:00
Alexander Rose
e57a19857f improved pdb reader, entity 2019-08-01 12:36:10 -07:00
Alexander Rose
469dd05cd9 added Column.ofIntTokens, .ofFloatTokens, .ofStringTokens 2019-08-01 12:34:47 -07:00
Alexander Rose
fb72db61bd code simplification 2019-08-01 12:34:01 -07:00
Alexander Rose
c285e30ee0 improved gro format reading 2019-08-01 10:09:20 -07:00
Alexander Rose
789a327322 improved large chain partitioning 2019-08-01 10:06:50 -07:00
Alexander Rose
b8d2021599 partition very large atomic chains per residue 2019-07-30 16:28:00 -07:00
Alexander Rose
36eae744af ensure that "single atom chains" units have same entity id 2019-07-30 16:24:54 -07:00
Alexander Rose
00df6ae52a don't show sequences > 10000 in widget 2019-07-30 16:23:05 -07:00
Alexander Rose
023b65572e viewer, option to load files without adding visuals 2019-07-30 16:22:34 -07:00
Alexander Rose
904e9b869c wip, cellpack loader improvements 2019-07-30 12:03:26 -07:00
Alexander Rose
6f204b960d Merge branch 'master' into meso 2019-07-30 09:12:08 -07:00
Alexander Rose
6217a51fa5 optimized toScriptExpression 2019-07-29 11:32:02 -07:00
Alexander Rose
f1edb05c5c fixed sorted-ranges spec 2019-07-29 10:03:54 -07:00
Alexander Rose
55bd27bb97 wip, structure selection tools 2019-07-26 17:32:51 -07:00
Alexander Rose
e2c9b601a6 wip, structure tools refactoring 2019-07-26 16:36:27 -07:00
Alexander Rose
238191660e Fixed StructureElement.Loci.toScriptExpression 2019-07-26 16:12:03 -07:00
Alexander Rose
0b175acc25 wip, structure tool controls 2019-07-26 15:50:26 -07:00
Alexander Rose
02865cbece trace-iterator to work with unit subsets 2019-07-26 12:44:40 -07:00
Alexander Rose
a3e14bf579 improved sorted-ranges and docs 2019-07-26 12:44:08 -07:00
Alexander Rose
1d502cbb54 loci remapping 2019-07-25 13:49:04 -07:00
Alexander Rose
4894b110b9 fixes, SortedRanges and AtomicPolymerTraceIterator 2019-07-25 10:58:42 -07:00
Alexander Rose
cefd0440a0 docs 2019-07-25 10:57:26 -07:00
David Sehnal
296bfb343e mol-plugin: updated 'select animation' icon 2019-07-24 07:45:41 +02:00
David Sehnal
bc91f0d3ff Support multiple models in StructureElement.Loci.toScriptExpression 2019-07-24 07:35:03 +02:00
David Sehnal
aaa8215a6d Fixed StructureElement.Loci.toScriptExpression 2019-07-24 07:11:38 +02:00
Alexander Rose
bf0b37895d handle modified base rings (DP, DZ) 2019-07-23 15:51:17 -07:00
Alexander Rose
0810ed411d nucleotide cartoon, detect Purin/Pyrimidin from geometry 2019-07-23 15:16:22 -07:00
Alexander Rose
ff8fec542c wip, overpaint controls, overpaint clearing 2019-07-23 12:04:02 -07:00
Alexander Rose
8fb7308572 wip, OverpaintControls 2019-07-22 17:35:17 -07:00
Alexander Rose
c9b7049532 fixed ExplodeStructureRepresentation3D 2019-07-22 17:34:46 -07:00
Alexander Rose
da71332de1 add 'sel.atom.all' macro 2019-07-22 17:34:30 -07:00
Alexander Rose
a0de8dd9f9 fixed seq widget update on object change 2019-07-22 12:14:03 -07:00
Alexander Rose
e226f27041 fixed wrong property use in seq widget 2019-07-22 12:04:27 -07:00
Alexander Rose
804a04d9f8 fixed coarse trace-iterator direction vectors 2019-07-22 11:52:00 -07:00
Alexander Rose
3be06bb3b6 wip, cellpack loader 2019-07-21 18:28:45 -07:00
Alexander Rose
c7b618c246 lighting demo 2019-07-21 18:13:10 -07:00
Alexander Rose
55d990962f better entity handling for pdb files 2019-07-20 15:58:22 -07:00
Alexander Rose
53e0a36539 improved readme deploy section 2019-07-19 18:38:13 -07:00
Alexander Rose
ed7a5219bf 0.2.5 2019-07-19 17:43:32 -07:00
Alexander Rose
8b49ccdc08 tweaked bottom and top height 2019-07-19 17:41:40 -07:00
Alexander Rose
04fd3ade5f improved canvas3d.resetCamera 2019-07-19 14:52:20 -07:00
Alexander Rose
48985cd49d improved scene commit handling for canvas3d and debug-helper 2019-07-19 14:40:45 -07:00
Alexander Rose
dd0707a8a5 fix renderer.spec test 2019-07-19 10:12:22 -07:00
Alexander Rose
b41ebcbbc8 Merge branch 'master' of https://github.com/molstar/molstar
# Conflicts:
#	src/mol-canvas3d/camera.ts
#	src/mol-canvas3d/canvas3d.ts
2019-07-19 09:50:03 -07:00
Alexander Rose
991d2e3a57 async gl repr object handling 2019-07-19 09:46:27 -07:00
Alexander Rose
7f4ac6782f camera tweaks 2019-07-19 09:45:26 -07:00
Alexander Rose
7e7e30a82e fix: removed useless z-sort 2019-07-19 09:44:11 -07:00
Alexander Rose
08e92f12d3 improved MolecularSurface calc: projectToriiRange 2019-07-19 09:42:09 -07:00
Alexander Rose
d713ea6a76 ModelIndex color theme 2019-07-19 09:41:06 -07:00
Alexander Rose
0924020f24 updated packages 2019-07-19 09:11:53 -07:00
David Sehnal
9f10af3ba6 proteopedia-wrapper: clipping commands; mol-canvas3d: camera.getFocus optional direction 2019-06-26 13:41:53 +02:00
Alexander Rose
f754026cc5 better entity placeholder 2019-06-22 12:26:28 -07:00
Alexander Rose
321d98f4c1 hetero sequence wrapper 2019-06-22 11:35:11 -07:00
Alexander Rose
58a49a8512 fix handling of structure loci selection 2019-06-22 08:12:07 -07:00
Alexander Rose
98bb9575b6 simplified polymer sequence wrapper 2019-06-22 07:45:04 -07:00
Alexander Rose
f10a135dea sequence view, better handling of missing residues, full structure loci 2019-06-22 07:34:20 -07:00
Alexander Rose
f300e524d1 updated packages 2019-06-21 17:36:38 -07:00
Alexander Rose
87028c0a0b 0.2.4 2019-06-21 17:21:59 -07:00
Alexander Rose
8ea23e6965 Merge branch 'master' into seq 2019-06-21 17:19:26 -07:00
Alexander Rose
f9d8942814 basic support for missing residues 2019-06-21 16:55:28 -07:00
Alexander Rose
b40df2f1e3 sequence, on state tree change improvments 2019-06-21 16:55:12 -07:00
Alexander Rose
884cb0d9a4 sequence widget refactoring 2019-06-21 12:54:22 -07:00
Alexander Rose
ef1ccd4286 cif schema updates 2019-06-21 12:44:28 -07:00
Alexander Rose
898abda373 sequence & interactivity tweaks 2019-06-21 11:06:49 -07:00
Alexander Rose
e42c664a8c fixes: StructureElement.Loci.union, Structure.parent 2019-06-21 10:56:06 -07:00
Alexander Rose
987bf47827 save interactivity props in state 2019-06-21 09:29:03 -07:00
Alexander Rose
6201dd1d74 improved xtal symmetry support for props & sequence 2019-06-21 09:11:38 -07:00
David Sehnal
5ed17ce4e5 proteopedia-wrapper: evolutionary coloring on current representation 2019-06-21 12:47:14 +02:00
Alexander Rose
e301eca9c2 wip, sequence view, select options 2019-06-20 15:50:31 -07:00
Alexander Rose
8a4ef015a2 added StructureElement.set 2019-06-20 15:42:45 -07:00
Alexander Rose
67f3f3fdbb improved AtomsQueryParams.unitTest and StructureProperties.unit 2019-06-20 15:42:23 -07:00
Alexander Rose
adc5b559cd various tweaks 2019-06-20 15:35:55 -07:00
Alexander Rose
bbaa637118 wip, sequence selector 2019-06-19 17:45:50 -07:00
Alexander Rose
a3094b4d19 wip, per-chain sequence widget 2019-06-19 17:03:20 -07:00
Alexander Rose
c3f937e113 sequence widget refactoring 2019-06-19 14:17:49 -07:00
Alexander Rose
04df327939 renamed lociExpansion to granularity 2019-06-19 12:30:19 -07:00
Alexander Rose
b1a0f46ade improved link loci handling for interactivity 2019-06-19 12:18:53 -07:00
Alexander Rose
389e249862 use PurePluginUIComponent for Residue 2019-06-19 11:48:36 -07:00
Alexander Rose
cfcf9f6818 wip, plugin interactivity 2019-06-18 17:28:57 -07:00
Alexander Rose
bcb8419f37 init repr3d and seq view marker with global selection 2019-06-18 14:31:22 -07:00
Alexander Rose
7d24bcf1dc tweaked resolution quality settings 2019-06-18 14:24:47 -07:00
Alexander Rose
8d0f7a2dc7 factored-out marker-action 2019-06-18 11:48:59 -07:00
Alexander Rose
a7cb7beaa8 Merge branch 'master' into seq 2019-06-18 09:04:33 -07:00
David Sehnal
3c9b82dc04 proteopedia-plugin: Load asym unit fix 2019-06-18 15:42:29 +02:00
David Sehnal
9dba6d5371 proteopedia-wrapper: tweak 2019-06-18 13:53:04 +02:00
David Sehnal
a65bba0969 proteopedia-wrapper: better HET group focusing 2019-06-18 13:39:23 +02:00
David Sehnal
175e009152 proteopedia-wrapper: fix assembly loading 2019-06-18 13:01:38 +02:00
Alexander Rose
897d17c8ed 0.2.3 2019-06-17 16:53:59 -07:00
Alexander Rose
ca866cfa3a Merge pull request #17 from JonStargaryen/encoding-config
Encoding and precision config
2019-06-17 16:48:55 -07:00
Alexander Rose
f6b2c0b2ba wip, sequence view 2019-06-17 16:44:38 -07:00
Alexander Rose
ea419c68ae removed old unused ui code 2019-06-17 15:41:03 -07:00
Alexander Rose
40cf348d40 simple (entity) sequence view 2019-06-17 15:40:51 -07:00
Alexander Rose
93ea759a71 StateTreeSpine.current tweaks 2019-06-17 15:39:12 -07:00
Alexander Rose
3e50377eb8 use as const to quickly make class props readonly 2019-06-17 15:29:37 -07:00
Alexander Rose
3fbd1f8dc4 StructureElementSelectionManager.tryGetRange in both directions 2019-06-17 15:27:22 -07:00
Alexander Rose
f03ce68513 plugin interaction helpers refactoring 2019-06-17 14:37:24 -07:00
Alexander Rose
50e2d542df add getModifiers input helper 2019-06-17 14:33:32 -07:00
Alexander Rose
e53e739d18 ignore non StructureElement loci in StructureElementSelectionManager 2019-06-17 14:32:47 -07:00
Alexander Rose
dfb7f7811f support coarse elements in atomGroups generator 2019-06-14 18:59:37 -07:00
Sebastian Bittrich
115824bbcf omits logging in test 2019-06-14 17:38:03 -07:00
Sebastian Bittrich
438de5760d trim() for safety in filter declaration 2019-06-14 17:37:47 -07:00
Sebastian Bittrich
3f765aedec removes FilteringDirective 2019-06-14 17:12:07 -07:00
Sebastian Bittrich
96144fb10f drops json filter definition in favor of text 2019-06-14 17:03:34 -07:00
Alexander Rose
cc34425712 add and use Structure.areParentsEquivalent 2019-06-13 16:36:18 -07:00
Alexander Rose
9d68838893 added OrderedSet.toString 2019-06-13 15:59:50 -07:00
Alexander Rose
6c68cebca0 show gfp chromophore traces 2019-06-13 13:32:34 -07:00
Alexander Rose
b3e784262d fix typos 2019-06-13 12:00:17 -07:00
Alexander Rose
2fdc22de71 plugin layout config improvements 2019-06-13 09:52:51 -07:00
Alexander Rose
77f0b0033f more recomended vscode extensions 2019-06-13 09:26:10 -07:00
Alexander Rose
69da5abb88 use staging url for rcsb graphql 2019-06-13 09:25:50 -07:00
Alexander Rose
a827e9a449 Merge branch 'master' of https://github.com/molstar/molstar 2019-06-13 09:08:10 -07:00
Alexander Rose
e74a29ae6a added recommeded vscode extensions 2019-06-12 23:21:59 -07:00
Alexander Rose
9b3d2f396e polymer-trace improvements 2019-06-12 23:14:05 -07:00
Alexander Rose
14cf7cc101 direction from/to derived residue prop 2019-06-12 23:10:50 -07:00
Alexander Rose
e5293c4d36 linalg .toString with precision arg 2019-06-12 23:09:24 -07:00
Sebastian Bittrich
3afe21a4c3 fix to whitelist categories when information on fields is present 2019-06-12 14:43:41 -07:00
Alexander Rose
bb50b69bb4 fix typo 2019-06-12 12:33:33 -07:00
Alexander Rose
af7c030338 removed unused HighlightEvent.prev 2019-06-12 12:33:18 -07:00
Alexander Rose
34b1eee876 fix webpack config 2019-06-12 12:32:00 -07:00
Sebastian Bittrich
c9cd1075d3 Merge branch 'master' into encoding-config 2019-06-12 12:22:20 -07:00
Sebastian Bittrich
4fa04f0ff8 changes comment style 2019-06-12 11:42:04 -07:00
Alexander Rose
75f6466fd7 webpack config update 2019-06-11 16:18:16 -07:00
Alexander Rose
503ffd80fd package updates 2019-06-11 15:04:15 -07:00
Alexander Rose
c8ac64a571 wip, server/model/preprocess 2019-06-10 16:15:14 -07:00
Alexander Rose
bf81b902bd chemCompBond creation improvements and prop 2019-06-10 15:30:13 -07:00
David Sehnal
b636cdf9cc Added volume streaming to proteopedia-wrapper example + vol. streaming behavior tweaks 2019-06-10 14:02:12 +02:00
David Sehnal
2d3b85825a mol-plugin: Allow to mount TransformUpdaterControl outside the main plugin 2019-06-10 13:13:30 +02:00
Alexander Rose
a5a34f39e0 don't use boxed primitives 2019-06-07 19:13:23 -07:00
Sebastian Bittrich
fe7e04f61b lowercase string parameters, filtering directive object 2019-06-07 16:56:04 -07:00
Sebastian Bittrich
1a14720e35 remove changes from package-lock 2019-06-07 15:45:12 -07:00
Sebastian Bittrich
18bf743ed2 white-/blacklist filtering 2019-06-07 15:39:07 -07:00
Sebastian Bittrich
f8d085a034 white-/blacklist filtering 2019-06-07 15:38:06 -07:00
Alexander Rose
94bf3a136c added viewer deployment script 2019-06-07 13:17:45 -07:00
Alexander Rose
9e8a8f3e71 updated viewer 2019-06-07 12:36:29 -07:00
Alexander Rose
45f9d93f3a fixed clearOverpaint 2019-06-07 12:18:29 -07:00
Alexander Rose
44a566fdf3 tweaked ObjectListControl ui 2019-06-07 12:18:16 -07:00
Sebastian Bittrich
d7f7770b7c drops browser test, creates spec 2019-06-07 11:49:31 -07:00
Sebastian Bittrich
df6b163505 replace encoding string with union type 2019-06-07 09:27:46 -07:00
Sebastian Bittrich
51f88fff71 merge master 2019-06-05 10:04:18 -07:00
Sebastian Bittrich
6b8db5abc6 reverts trygetencoder order to original 2019-06-05 10:01:46 -07:00
Sebastian Bittrich
37a0b07d56 stub for encoding config 2019-06-04 17:52:02 -07:00
451 changed files with 19527 additions and 6836 deletions

18
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"firsttris.vscode-jest-runner",
"ms-vscode.vscode-typescript-tslint-plugin",
"msjsdiag.debugger-for-chrome",
"slevesque.shader",
"stpn.vscode-graphql",
"wayou.vscode-todo-highlight"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Mol* Viewer",
"url": "http://localhost:1338/build/viewer/index.html",
"sourceMaps": true,
"webRoot": "${workspaceFolder}"
}
]
}

View File

@@ -85,7 +85,7 @@ and navigate to `build/viewer`
Install CIFTools `npm install ciftools -g`
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
cifschema -mip ../../../../mol-data-o src/mol-io/reader/cif/schema/ccd.ts -p CCD
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
**GraphQL schemas**
@@ -95,7 +95,7 @@ Install CIFTools `npm install ciftools -g`
### Other scripts
**Create chem comp bond table**
export NODE_PATH="lib"; node --max-old-space-size=8192 build/src/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
export NODE_PATH="lib"; node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
**Test model server**
@@ -133,14 +133,19 @@ To get syntax highlighting for shader and graphql files add the following to Vis
## Publish
## Prerelease
### Prerelease
npm version prerelease # asumes the current version ends with '-dev.X'
npm publish --tag next
## Release
### Release
npm version 0.X.0 # provide valid semver string
npm publish
## Deploy
npm run test
npm run build
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
## Contributing
Just open an issue or make a pull request. All contributions are welcome.

View File

@@ -4,7 +4,7 @@ const path = require('path')
const basePath = path.join(__dirname, '..', '..', 'src', 'mol-model-props', 'rcsb', 'graphql')
generate({
schema: 'http://rest-dev.rcsb.org/graphql',
schema: 'http://rest-staging.rcsb.org/graphql',
documents: {
[path.join(basePath, 'symmetry.gql.ts')]: {
loader: path.join(__dirname, 'loader.js')

4104
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.2.2",
"version": "0.3.7",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -15,14 +15,16 @@
"test": "npm run lint && jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"build-tsc": "tsc",
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html}\" lib/",
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/",
"build-webpack": "webpack --mode production",
"watch": "concurrently --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
"watch-tsc": "tsc -watch",
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html}\" lib/ --watch",
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --display minimal",
"serve": "http-server -p 1338",
"model-server": "node lib/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
"volume-server": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"preversion": "npm run test",
"postversion": "git push && git push --tags",
"prepublishOnly": "npm run test && npm run build"
@@ -62,53 +64,55 @@
"license": "MIT",
"devDependencies": {
"benchmark": "^2.1.4",
"circular-dependency-plugin": "^5.0.2",
"concurrently": "^4.1.0",
"circular-dependency-plugin": "^5.2.0",
"concurrently": "^5.0.0",
"cpx": "^1.5.0",
"css-loader": "^2.1.1",
"css-loader": "^3.2.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^3.0.1",
"file-loader": "^4.2.0",
"fs-extra": "^8.1.0",
"graphql-code-generator": "^0.18.2",
"graphql-codegen-time": "^0.18.2",
"graphql-codegen-typescript-template": "^0.18.2",
"jest": "^24.8.0",
"http-server": "^0.11.1",
"jest": "^24.9.0",
"jest-raw-loader": "^1.0.1",
"mini-css-extract-plugin": "^0.7.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"raw-loader": "^2.0.0",
"raw-loader": "^3.1.0",
"resolve-url-loader": "^3.1.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"ts-jest": "^24.0.2",
"tslint": "^5.17.0",
"typescript": "^3.5.1",
"uglify-js": "^3.6.0",
"util.promisify": "^1.0.0",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2"
"sass-loader": "^8.0.0",
"simple-git": "^1.126.0",
"style-loader": "^1.0.0",
"ts-jest": "^24.1.0",
"tslint": "^5.20.0",
"typescript": "^3.6.4",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9"
},
"dependencies": {
"@types/argparse": "^1.0.36",
"@types/benchmark": "^1.0.31",
"@types/compression": "0.0.36",
"@types/express": "^4.16.1",
"@types/jest": "^24.0.13",
"@types/node": "^12.0.4",
"@types/node-fetch": "^2.3.4",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"@types/swagger-ui-dist": "3.0.0",
"@types/webgl2": "0.0.4",
"@types/compression": "1.0.1",
"@types/express": "^4.17.1",
"@types/jest": "^24.0.19",
"@types/node": "^12.11.1",
"@types/node-fetch": "^2.5.2",
"@types/react": "^16.9.9",
"@types/react-dom": "^16.9.2",
"@types/swagger-ui-dist": "3.0.3",
"@types/webgl2": "0.0.5",
"argparse": "^1.0.10",
"compression": "^1.7.4",
"express": "^4.17.1",
"graphql": "^14.3.1",
"graphql": "^14.5.8",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"rxjs": "^6.5.2",
"swagger-ui-dist": "^3.22.2",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"rxjs": "^6.5.3",
"swagger-ui-dist": "^3.24.0",
"util.promisify": "^1.0.0",
"xhr2": "^0.2.0"
}
}

59
scripts/deploy.js Normal file
View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
const git = require('simple-git')
const path = require('path')
const fs = require("fs")
const fse = require("fs-extra")
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
const buildDir = path.resolve(__dirname, '../build/')
const deployDir = path.resolve(buildDir, 'deploy/')
const localPath = path.resolve(deployDir, 'molstar.github.io/')
function log(command, stdout, stderr) {
if (command) {
console.log('\n###', command)
stdout.pipe(process.stdout)
stderr.pipe(process.stderr)
}
}
function copyViewer() {
console.log('\n###', 'copy viewer files')
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
const viewerDeployPath = path.resolve(localPath, 'viewer/')
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
}
if (!fs.existsSync(localPath)) {
console.log('\n###', 'create localPath')
fs.mkdirSync(localPath, { recursive: true })
}
process.chdir(localPath);
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
console.log('\n###', 'clone repository')
git()
.outputHandler(log)
.clone(remoteUrl, localPath)
.fetch(['--all'])
.exec(copyViewer)
.add(['-A'])
.commit('updated viewer')
.push()
} else {
console.log('\n###', 'update repository')
git()
.outputHandler(log)
.fetch(['--all'])
.reset(['--hard', 'origin/master'])
.exec(copyViewer)
.add(['-A'])
.commit('updated viewer')
.push()
}

View File

@@ -19,4 +19,12 @@ export class BasicWrapperControls extends PluginUIComponent {
<TransformUpdaterControl nodeRef='ihm-visual' header={{ name: 'I/HM Visual' }} initiallyCollapsed={true} />
</div>;
}
}
export class CustomToastMessage extends PluginUIComponent {
render() {
return <>
Custom <i>Toast</i> content. No timeout.
</>;
}
}

View File

@@ -33,18 +33,18 @@ export namespace StateHelper {
};
export function selectChain(b: StateBuilder.To<PSO.Molecule.Structure>, auth_asym_id: string) {
const query = MS.struct.generator.atomGroups({
const expression = MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
})
return b.apply(StateTransforms.Model.StructureSelection, { query, label: `Chain ${auth_asym_id}` });
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Chain ${auth_asym_id}` });
}
export function select(b: StateBuilder.To<PSO.Molecule.Structure>, query: Expression) {
return b.apply(StateTransforms.Model.StructureSelection, { query });
export function select(b: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression) {
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression });
}
export function selectSurroundingsOfFirstResidue(b: StateBuilder.To<PSO.Molecule.Structure>, comp_id: string, radius: number) {
const query = MS.struct.modifier.includeSurroundings({
const expression = MS.struct.modifier.includeSurroundings({
0: MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
@@ -53,7 +53,7 @@ export namespace StateHelper {
]),
radius
})
return b.apply(StateTransforms.Model.StructureSelection, { query, label: `Surr. ${comp_id} (${radius} ang)` });
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Surr. ${comp_id} (${radius} ang)` });
}
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
@@ -83,9 +83,9 @@ export namespace StateHelper {
return visualRoot;
}
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, query: Expression, coloring?: BuiltInColorThemeName) {
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, coloring?: BuiltInColorThemeName) {
visualRoot
.apply(StateTransforms.Model.StructureSelection, { query })
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', void 0, coloring), { tags: 'het-visual' });
return visualRoot;

View File

@@ -105,10 +105,18 @@
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
addHeader('Interactivity');
addControl('Highlight seq_id=7', () => BasicMolStarWrapper.interactivity.highlightOn());
addControl('Clear Highlight', () => BasicMolStarWrapper.interactivity.clearHighlight());
addHeader('Tests');
addControl('Static Superposition', () => BasicMolStarWrapper.tests.staticSuperposition());
addControl('Dynamic Superposition', () => BasicMolStarWrapper.tests.dynamicSuperposition());
addControl('Validation Tooltip', () => BasicMolStarWrapper.tests.toggleValidationTooltip());
addControl('Show Toasts', () => BasicMolStarWrapper.tests.showToasts());
addControl('Hide Toasts', () => BasicMolStarWrapper.tests.hideToasts());
////////////////////////////////////////////////////////

View File

@@ -11,12 +11,16 @@ import { PluginCommands } from '../../mol-plugin/command';
import { StateTransforms } from '../../mol-plugin/state/transforms';
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
import { Color } from '../../mol-util/color';
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
import { StateBuilder, StateTransform } from '../../mol-state';
import { StripedResidues } from './coloring';
// import { BasicWrapperControls } from './controls';
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props';
import { CustomToastMessage } from './controls';
import { EmptyLoci } from '../../mol-model/loci';
import { StructureSelection } from '../../mol-model/structure';
import { Script } from '../../mol-script/script';
require('mol-plugin/skin/light.scss')
type SupportedFormats = 'cif' | 'pdb'
@@ -61,7 +65,7 @@ class BasicWrapper {
}
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon'), { ref: 'seq-visual' });
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
@@ -142,6 +146,22 @@ class BasicWrapper {
}
}
interactivity = {
highlightOn: () => {
const seq_id = 7;
const data = (this.plugin.state.dataState.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
}), data);
const loci = StructureSelection.toLociWithSourceUnits(sel);
this.plugin.interactivity.lociHighlights.highlightOnly({ loci });
},
clearHighlight: () => {
this.plugin.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
}
}
tests = {
staticSuperposition: async () => {
const state = this.plugin.state.dataState;
@@ -152,6 +172,28 @@ class BasicWrapper {
dynamicSuperposition: async () => {
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.plugin.state.dataState, ref: StateTransform.RootRef });
await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
},
toggleValidationTooltip: async () => {
const state = this.plugin.state.behaviorState;
const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip }));
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
},
showToasts: () => {
PluginCommands.Toast.Show.dispatch(this.plugin, {
title: 'Toast 1',
message: 'This is an example text, timeout 3s',
key: 'toast-1',
timeoutMs: 3000
});
PluginCommands.Toast.Show.dispatch(this.plugin, {
title: 'Toast 2',
message: CustomToastMessage,
key: 'toast-2'
});
},
hideToasts: () => {
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-1' });
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-2' });
}
}
}

View File

@@ -86,7 +86,7 @@ export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[]
const query = compile<StructureSelection>(pivot);
const xs = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
const selections = xs.map(s => StructureSelection.toLoci(query(new QueryContext(s.obj!.data))));
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.obj!.data))));
const transforms = superposeStructures(selections);
const visuals = state.build();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -15,12 +15,13 @@ const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
import { Progress } from '../../mol-task'
import { Database, Table, DatabaseCollection, Column } from '../../mol-data/db'
import { Database, Table, DatabaseCollection } from '../../mol-data/db'
import { CIF } from '../../mol-io/reader/cif'
import { CifWriter } from '../../mol-io/writer/cif'
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'
import { SetUtils } from '../../mol-util/set'
import { DefaultMap } from '../../mol-util/map'
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
export async function ensureAvailable(path: string, url: string) {
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
@@ -74,16 +75,6 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>,
type CCB = Table<CCD_Schema['chem_comp_bond']>
type CCA = Table<CCD_Schema['chem_comp_atom']>
const ChemCompBond_Schema = {
comp_id: CCD_Schema['chem_comp_bond'].comp_id,
atom_id_1: CCD_Schema['chem_comp_bond'].atom_id_1,
atom_id_2: CCD_Schema['chem_comp_bond'].atom_id_2,
value_order: CCD_Schema['chem_comp_bond'].value_order,
pdbx_aromatic_flag: CCD_Schema['chem_comp_bond'].pdbx_aromatic_flag,
pdbx_stereo_config: CCD_Schema['chem_comp_bond'].pdbx_stereo_config,
molstar_protonation_variant: Column.Schema.Str()
}
function ccbKey(compId: string, atomId1: string, atomId2: string) {
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`
}
@@ -202,14 +193,14 @@ async function createBonds() {
}
}
const bondTable = Table.ofArrays(ChemCompBond_Schema, {
const bondTable = Table.ofArrays(mmCIF_chemCompBond_schema, {
comp_id, atom_id_1, atom_id_2, value_order,
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
})
const bondDatabase = Database.ofTables(
TABLE_NAME,
{ chem_comp_bond: ChemCompBond_Schema },
{ chem_comp_bond: mmCIF_chemCompBond_schema },
{ chem_comp_bond: bondTable }
)
@@ -220,12 +211,15 @@ async function run(out: string, binary = false) {
const bonds = await createBonds()
const cif = getEncodedCif(TABLE_NAME, bonds, binary)
if (!fs.existsSync(path.dirname(out))) {
fs.mkdirSync(path.dirname(out));
}
writeFile(out, cif)
}
const TABLE_NAME = 'CHEM_COMP_BONDS'
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'data')
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data')
const CCD_PATH = path.join(DATA_DIR, 'components.cif')
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif')
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif'

View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Lighting Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
position: absolute;
left: 160px;
top: 100px;
width: 600px;
height: 600px;
border: 1px solid #ccc;
}
#controls {
position: absolute;
width: 150px;
top: 100px;
left: 780px;
}
#controls > button {
display: block;
width: 100%;
text-align: left;
margin: 5px 0px;
}
#controls > input, #controls > select {
width: 100%;
display: block;
}
</style>
<link rel="stylesheet" type="text/css" href="app.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id='controls'></div>
<div id="app"></div>
<script>
LightingDemo.init('app')
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
addHeader('Example PDB IDs');
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' }));
addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' }));
addSeparator()
addHeader('Lighting Presets');
addControl('Illustrative', () => LightingDemo.setPreset('illustrative'));
addControl('Standard', () => LightingDemo.setPreset('standard'));
addControl('Ambient Occlusion', () => LightingDemo.setPreset('occlusion'));
////////////////////////////////////////////////////////
function $(id) { return document.getElementById(id); }
function addControl(label, action) {
var btn = document.createElement('button');
btn.onclick = action;
btn.innerText = label;
$('controls').appendChild(btn);
}
function addSeparator() {
var hr = document.createElement('br');
$('controls').appendChild(hr);
}
function addHeader(header) {
var h = document.createElement('h3');
h.innerText = header;
$('controls').appendChild(h);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,166 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
import './index.html'
import { PluginContext } from '../../../mol-plugin/context';
import { PluginCommands } from '../../../mol-plugin/command';
import { StateTransforms } from '../../../mol-plugin/state/transforms';
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects';
import { StateBuilder } from '../../../mol-state';
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
require('mol-plugin/skin/light.scss')
type SupportedFormats = 'cif' | 'pdb'
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
const Canvas3DPresets = {
illustrative: {
multiSample: {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusionEnable: true,
occlusionBias: 0.8,
occlusionKernelSize: 6,
outlineEnable: true,
},
renderer: {
ambientIntensity: 1,
lightIntensity: 0,
}
},
occlusion: {
multiSample: {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusionEnable: true,
occlusionBias: 0.8,
occlusionKernelSize: 6,
outlineEnable: false,
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
}
},
standard: {
multiSample: {
mode: 'off' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusionEnable: false,
outlineEnable: false,
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
}
}
}
type Canvas3DPreset = keyof typeof Canvas3DPresets
function getPreset(preset: Canvas3DPreset) {
switch (preset) {
case 'illustrative': return Canvas3DPresets['illustrative']
case 'standard': return Canvas3DPresets['standard']
case 'occlusion': return Canvas3DPresets['occlusion']
}
}
class LightingDemo {
plugin: PluginContext;
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
}
});
this.setPreset('illustrative');
}
setPreset(preset: Canvas3DPreset) {
const props = getPreset(preset)
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
...props,
multiSample: {
...this.plugin.canvas3d.props.multiSample,
...props.multiSample
},
renderer: {
...this.plugin.canvas3d.props.renderer,
...props.renderer
},
postprocessing: {
...this.plugin.canvas3d.props.postprocessing,
...props.postprocessing
},
}});
}
private download(b: StateBuilder.To<PSO.Root>, url: string) {
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
}
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
}
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' });
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
return visualRoot;
}
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
let loadType: 'full' | 'update' = 'full';
const state = this.plugin.state.dataState;
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
loadType = 'full';
} else if (this.loadedParams.url === url) {
if (state.select('asm').length > 0) loadType = 'update';
}
let tree: StateBuilder.Root;
if (loadType === 'full') {
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
tree = state.build();
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
} else {
tree = state.build();
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
}
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
this.loadedParams = { url, format, assemblyId };
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
}
}
(window as any).LightingDemo = new LightingDemo();

View File

@@ -112,7 +112,7 @@ const state: State = {
function formatParams(def: QueryDefinition) {
const prms = Object.create(null);
for (const p of def.params) {
for (const p of def.jsonParams) {
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
}
return JSON.stringify(prms, void 0, 2);

View File

@@ -19,7 +19,7 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'conditioned': return getParams(param.conditionParams, offset);
case 'multi-select': return `Array of ${oToS(param.options)}`;
case 'color': return 'Color as 0xrrggbb';
case 'color-scale': return `One of ${oToS(param.options)}`;
case 'color-list': return `One of ${oToS(param.options)}`;
case 'vec3': return `3D vector [x, y, z]`;
case 'file': return `JavaScript File Handle`;
case 'select': return `One of ${oToS(param.options)}`;
@@ -30,7 +30,7 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'line-graph': return `A list of 2d vectors [xi, yi][]`;
case 'object-list': return `Array of\n${paramInfo(PD.Group(param.element), offset + 2)}`;
// TODO: support more languages
case 'script-expression': return `An expression in the specified language { language: 'mol-script', expressiong: string }`;
case 'script': return `An expression in the specified language { language: 'mol-script', expressiong: string }`;
default:
const _: never = param;
console.warn(`${_} has no associated UI component`);

View File

@@ -15,6 +15,7 @@ import { OrderedSet } from '../../mol-data/int';
import { openCif, downloadCif } from './helpers';
import { Vec3 } from '../../mol-math/linear-algebra';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { Sequence } from '../../mol-model/sequence';
async function downloadFromPdb(pdb: string) {
@@ -110,9 +111,10 @@ export function printSequence(model: Model) {
console.log('\nSequence\n=============');
const { byEntityKey } = model.sequence;
for (const key of Object.keys(byEntityKey)) {
const seq = byEntityKey[+key];
console.log(`${seq.entityId} (${seq.sequence.kind} ${seq.num.value(0)} (offset ${seq.sequence.offset}), ${seq.num.value(seq.num.rowCount - 1)}) (${seq.compId.value(0)}, ${seq.compId.value(seq.compId.rowCount - 1)})`);
console.log(`${seq.sequence.sequence}`);
const { sequence, entityId } = byEntityKey[+key];
const { seqId, compId } = sequence
console.log(`${entityId} (${sequence.kind} ${seqId.value(0)} (offset ${sequence.offset}), ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
console.log(`${Sequence.getSequenceString(sequence)}`);
}
console.log();
}
@@ -146,7 +148,7 @@ export function printRings(structure: Structure) {
export function printUnits(structure: Structure) {
console.log('\nUnits\n=============');
const l = StructureElement.create();
const l = StructureElement.Location.create();
for (const unit of structure.units) {
l.unit = unit;
@@ -159,14 +161,14 @@ export function printUnits(structure: Structure) {
console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
const props = StructureProperties.coarse;
const seq = l.unit.model.sequence;
const modelSeq = l.unit.model.sequence;
for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
l.element = OrderedSet.getAt(elements, j);
const residues: string[] = [];
const start = props.seq_id_begin(l), end = props.seq_id_end(l);
const compId = seq.byEntityKey[props.entityKey(l)].compId.value;
const compId = modelSeq.byEntityKey[props.entityKey(l)].sequence.compId.value;
for (let e = start; e <= end; e++) residues.push(compId(e));
console.log(`${props.asym_id(l)}:${start}-${end} (${residues.join('-')}) ${props.asym_id(l)} [${props.x(l).toFixed(2)}, ${props.y(l).toFixed(2)}, ${props.z(l).toFixed(2)}]`);
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Ludovic Autin <autin@scripps.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Quat, Mat4 } from '../../../../mol-math/linear-algebra';
import { NumberArray } from '../../../../mol-util/type-helpers';
interface Frame {
t: Vec3,
r: Vec3,
s: Vec3,
}
const a0Tmp = Vec3()
const a1Tmp = Vec3()
const a2Tmp = Vec3()
const a3Tmp = Vec3()
function CubicInterpolate(out: Vec3, y0: Vec3, y1: Vec3, y2: Vec3, y3: Vec3, mu: number): Vec3 {
const mu2 = mu * mu;
Vec3.sub(a0Tmp, y3, y2)
Vec3.sub(a0Tmp, a0Tmp, y0)
Vec3.add(a0Tmp, a0Tmp, y1)
Vec3.sub(a1Tmp, y0, y1)
Vec3.sub(a1Tmp, a1Tmp, a0Tmp)
Vec3.sub(a2Tmp, y2, y0)
Vec3.copy(a3Tmp, y1)
out[0] = a0Tmp[0] * mu * mu2 + a1Tmp[0] * mu2 + a2Tmp[0] * mu + a3Tmp[0]
out[1] = a0Tmp[1] * mu * mu2 + a1Tmp[1] * mu2 + a2Tmp[1] * mu + a3Tmp[1]
out[2] = a0Tmp[2] * mu * mu2 + a1Tmp[2] * mu2 + a2Tmp[2] * mu + a3Tmp[2]
return out
}
const cp0 = Vec3()
const cp1 = Vec3()
const cp2 = Vec3()
const cp3 = Vec3()
const currentPosition = Vec3()
function ResampleControlPoints(points: NumberArray, segmentLength: number) {
const nP = points.length / 3
// insert a point at the end and at the begining
// 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[] = []
// resampledControlPoints.Add(controlPoints[0]);
// resampledControlPoints.Add(controlPoints[1]);
let idx = 1
// const currentPosition = Vec3.create(points[idx * 3], points[idx * 3 + 1], points[idx * 3 + 2])
Vec3.fromArray(currentPosition, points, idx * 3)
let lerpValue = 0.0
// Normalize the distance between control points
while (true) {
if (idx + 2 >= nP) break
Vec3.fromArray(cp0, points, (idx - 1) * 3)
Vec3.fromArray(cp1, points, idx * 3)
Vec3.fromArray(cp2, points, (idx + 1) * 3)
Vec3.fromArray(cp3, points, (idx + 2) * 3)
// const cp0 = Vec3.create(points[(idx-1)*3], points[(idx-1)*3+1], points[(idx-1)*3+2]) // controlPoints[currentPointId - 1];
// const cp1 = Vec3.create(points[idx*3], points[idx*3+1], points[idx*3+2]) // controlPoints[currentPointId];
// const cp2 = Vec3.create(points[(idx+1)*3], points[(idx+1)*3+1], points[(idx+1)*3+2]) // controlPoints[currentPointId + 1];
// const cp3 = Vec3.create(points[(idx+2)*3], points[(idx+2)*3+1], points[(idx+2)*3+2]); // controlPoints[currentPointId + 2];
let found = false
for (; lerpValue <= 1; lerpValue += 0.01) {
// lerp?slerp
// let candidate:Vec3 = Vec3.lerp(Vec3.zero(), cp0, cp1, lerpValue);
// const candidate:Vec3 = Vec3.bezier(Vec3.zero(), cp0, cp1, cp2, cp3, lerpValue);
const candidate = CubicInterpolate(Vec3(), cp0, cp1, cp2, cp3, lerpValue)
const d = Vec3.distance(currentPosition, candidate);
if (d > segmentLength) {
resampledControlPoints.push(candidate)
Vec3.copy(currentPosition, candidate)
found = true
break
}
}
if (!found) {
lerpValue = 0
idx += 1
}
}
return resampledControlPoints
}
const prevV = Vec3()
const tmpV1 = Vec3()
const tmpV2 = Vec3()
const tmpV3 = Vec3()
// easier to align to theses normals
function GetSmoothNormals(points: Vec3[]) {
const nP: number = points.length;
const smoothNormals: Vec3[] = []
if (points.length < 3) {
for (let i = 0; i < points.length; ++i)
smoothNormals.push(Vec3.normalize(Vec3(), points[i]))
return smoothNormals;
}
let p0 = points[0]
let p1 = points[1]
let p2 = points[2]
const p21 = Vec3.sub(tmpV1, p2, p1)
const p01 = Vec3.sub(tmpV2, p0, p1)
const p0121 = Vec3.cross(tmpV3, p01, p21)
Vec3.normalize(prevV, p0121)
smoothNormals.push(Vec3.clone(prevV))
for (let i = 1; i < points.length - 1; ++i) {
p0 = points[i - 1]
p1 = points[i]
p2 = points[i + 1]
const t = Vec3.normalize(tmpV1, Vec3.sub(tmpV1, p2 , p0))
const b = Vec3.normalize(tmpV2, Vec3.cross(tmpV2, t, prevV))
const n = Vec3.normalize(Vec3(), Vec3.cross(tmpV3, t, b))
Vec3.negate(n, n)
Vec3.copy(prevV, n)
smoothNormals.push(n)
}
const last = Vec3()
Vec3.normalize(last, Vec3.cross(last,
Vec3.sub(tmpV1, points[nP - 3], points[nP-2]),
Vec3.sub(tmpV2, points[nP-2] , points[nP-1]))
)
smoothNormals.push(last)
return smoothNormals;
}
const frameTmpV1 = Vec3()
const frameTmpV2 = Vec3()
const frameTmpV3 = Vec3()
function getFrame(reference: Vec3, tangent: Vec3) {
const t = Vec3.normalize(Vec3(), tangent);
// make reference vector orthogonal to tangent
const proj_r_to_t = Vec3.scale(
frameTmpV1, tangent, Vec3.dot(reference, tangent) / Vec3.dot(tangent, tangent)
)
const r = Vec3.normalize(Vec3(), Vec3.sub(frameTmpV2, reference, proj_r_to_t))
// make bitangent vector orthogonal to the others
const s = Vec3.normalize(Vec3(), Vec3.cross(frameTmpV3, t, r))
return { t, r, s }
}
const mfTmpV1 = Vec3()
const mfTmpV2 = Vec3()
const mfTmpV3 = Vec3()
const mfTmpV4 = Vec3()
const mfTmpV5 = Vec3()
const mfTmpV6 = Vec3()
const mfTmpV7 = Vec3()
const mfTmpV8 = Vec3()
const mfTmpV9 = Vec3()
// easier to align to theses normals
// https://github.com/bzamecnik/gpg/blob/master/rotation-minimizing-frame/rmf.py
function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
const frames: Frame[] = [];
const t0 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[1], points[0]))
frames.push(getFrame(normals[0], t0))
for (let i = 0; i< points.length-2; ++i) {
const t2 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[i+2], points[i+1]))
const v1 = Vec3.sub(mfTmpV2, points[i + 1], points[i]) // this is tangeant
const c1 = Vec3.dot(v1, v1)
// compute r_i^L = R_1 * r_i
const v1r = Vec3.scale(mfTmpV3, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].r))
const ref_L_i = Vec3.sub(mfTmpV4, frames[i].r, v1r)
// compute t_i^L = R_1 * t_i
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 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))
const ref_next = Vec3.sub(mfTmpV9, ref_L_i, v2l) // ref_L_i - (2 / c2) * v2.dot(ref_L_i) * v2
frames.push(getFrame(ref_next, t2)) // frames.append(Frame(ref_next, tangents[i+1]))
}
return frames;
}
const rpTmpVec1 = Vec3()
export function getMatFromResamplePoints(points: NumberArray) {
const segmentLength = 3.4
const new_points = ResampleControlPoints(points, 3.4)
const npoints = new_points.length
const new_normal = GetSmoothNormals(new_points)
const frames = GetMiniFrame(new_points, new_normal)
const limit = npoints
const transforms: Mat4[] = []
const pti = Vec3.copy(rpTmpVec1, new_points[0]);
// console.log(new_points.length)
// console.log(points.length/3)
// console.log(limit)
// console.log(segmentLength)
for (let i = 0; i<npoints-2; ++i) {
const pti1: Vec3 = new_points[i+1] // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]);
const d = Vec3.distance(pti, pti1)
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 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);
// Vec3.makeRotation(Mat4.zero(),Vec3.create(0,0,1),frames[i].t);//
Mat4.setTranslation(m, pti1)
// let m2:Mat4 = GetTubePropertiesMatrix(pti,pti1);
// let q:Quat = Quat.rotationTo(Quat.zero(), Vec3.create(0,1,0),Vec3.create(0,0,1))
// m2=Mat4.mul(Mat4.identity(),Mat4.fromQuat(Mat4.zero(),q),m2);
transforms.push(m)
Vec3.copy(pti, pti1)
}
if (transforms.length >= limit) break
}
return transforms
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Quat } from '../../../../mol-math/linear-algebra';
export interface CellPack {
cell: Cell
packings: CellPacking[]
}
export interface CellPacking {
name: string,
location: 'surface' | 'interior' | 'cytoplasme',
ingredients: Packing['ingredients']
}
//
export interface Cell {
recipe: Recipe
cytoplasme?: Packing
compartments?: { [key: string]: Compartment }
}
export interface Recipe {
setupfile: string
/** First entry is name, secound is path: [name: string, path: string][] */
paths: [string, string][]
version: string
name: string
}
export interface Compartment {
surface?: Packing
interior?: Packing
}
export interface Packing {
ingredients: { [key: string]: Ingredient }
}
export interface Ingredient {
source: IngredientSource
results: [Vec3, Quat][]
name: string
positions?: [Vec3[]] // why wrapped in an extra array?
radii?: [number[]] // why wrapped in an extra array?
/** Number of `curveX` properties in the object where `X` is a 0-indexed number */
nbCurve?: number
/** Curve properties are Vec3[] but that is not expressable in TypeScript */
[curveX: string]: unknown
}
export interface IngredientSource {
pdb: string
transform: { center: boolean, translate?: Vec3 }
biomt?: boolean
}

View File

@@ -0,0 +1,474 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StateAction } 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, CellPacking, Cell } from './data';
import { getFromPdb, getFromCellPackDB } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext } from '../../../../mol-model/structure';
import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif';
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { Task } from '../../../../mol-task';
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
import { distinctColors } from '../../../../mol-util/color/distinct';
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
import { getMatFromResamplePoints } from './curve';
import { compile } from '../../../../mol-script/runtime/query/compiler';
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
import { ThemeRegistryContext } from '../../../../mol-theme/theme';
import { ColorTheme } from '../../../../mol-theme/color';
import { _parse_mmCif } from '../../../../mol-model-formats/structure/mmcif/parser';
import { ModelFormat } from '../../../../mol-model-formats/structure/format';
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
import { Column } from '../../../../mol-data/db';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/results/${fileName}`
}
async function getModel(id: string, baseUrl: string) {
let model: Model;
if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
// return
const cif = await getFromPdb(id)
model = (await trajectoryFromMmCIF(cif).run())[0]
} else {
const pdb = await getFromCellPackDB(id, baseUrl)
model = (await trajectoryFromPDB(pdb).run())[0]
}
return model
}
async function getStructure(model: Model, props: { assembly?: string } = {}) {
let structure = Structure.ofModel(model)
const { assembly } = props
if (assembly) {
structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
}
const query = MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
})
])
const compiled = compile<StructureSelection>(query)
const result = compiled(new QueryContext(structure))
structure = StructureSelection.unionStructure(result)
return structure
}
function getTransform(trans: Vec3, rot: Quat) {
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
Mat4.transpose(m, m)
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
Mat4.setTranslation(m, trans)
return m
}
function getResultTransforms(results: Ingredient['results']) {
return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
}
function getCurveTransforms(ingredient: Ingredient) {
const n = ingredient.nbCurve || 0
const instances: Mat4[] = []
for (let i = 0; i < n; ++i) {
const cname = `curve${i}`
if (!(cname in ingredient)) {
// console.warn(`Expected '${cname}' in ingredient`)
continue
}
const _points = ingredient[cname] as Vec3[]
if (_points.length <= 2) {
// TODO handle curve with 2 or less points
continue
}
const points = new Float32Array(_points.length * 3)
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3)
const newInstances = getMatFromResamplePoints(points)
instances.push(...newInstances)
}
return instances
}
function getAssembly(transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder()
const { units } = structure;
for (let i = 0, il = transforms.length; i < il; ++i) {
const id = `${i + 1}`
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
for (const unit of units) {
builder.addWithOperator(unit, op)
}
}
return builder.getStructure();
}
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
const d = model.sourceData.data.atom_site
const n = d._rowCount
const rowCount = n * transforms.length
const { offsets, count } = model.atomicHierarchy.chainAtomSegments
const x = d.Cartn_x.toArray()
const y = d.Cartn_y.toArray()
const z = d.Cartn_z.toArray()
const Cartn_x = new Float32Array(rowCount)
const Cartn_y = new Float32Array(rowCount)
const Cartn_z = new Float32Array(rowCount)
const map = new Uint32Array(rowCount)
const seq = new Int32Array(rowCount)
let offset = 0
for (let c = 0; c < count; ++c) {
const cStart = offsets[c]
const cEnd = offsets[c + 1]
const cLength = cEnd - cStart
for (let t = 0, tl = transforms.length; t < tl; ++t) {
const m = transforms[t]
for (let j = cStart; j < cEnd; ++j) {
const i = offset + j - cStart
const xj = x[j], yj = y[j], zj = z[j]
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12]
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13]
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14]
map[i] = j
seq[i] = t + 1
}
offset += cLength
}
}
function multColumn<T>(column: Column<T>) {
const array = column.toArray()
return Column.ofLambda({
value: row => array[map[row]],
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
rowCount, schema: column.schema
})
}
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
auth_seq_id: CifField.ofNumbers(seq),
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
Cartn_x: CifField.ofNumbers(Cartn_x),
Cartn_y: CifField.ofNumbers(Cartn_y),
Cartn_z: CifField.ofNumbers(Cartn_z),
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
id: CifField.ofColumn(Column.ofLambda({
value: row => row,
areValuesEqual: (rowA, rowB) => rowA === rowB,
rowCount, schema: d.id.schema,
})),
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
label_seq_id: CifField.ofNumbers(seq),
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
}
const categories = {
entity: CifCategory.ofTable('entity', model.sourceData.data.entity),
chem_comp: CifCategory.ofTable('chem_comp', model.sourceData.data.chem_comp),
atom_site: CifCategory.ofFields('atom_site', _atom_site)
}
return {
header: name,
categoryNames: Object.keys(categories),
categories
};
}
async function getCurve(name: string, transforms: Mat4[], model: Model) {
const cif = getCifCurve(name, transforms, model)
const curveModelTask = Task.create('Curve Model', async ctx => {
const format = ModelFormat.mmCIF(cif)
const models = await _parse_mmCif(format, ctx)
return models[0]
})
const curveModel = await curveModelTask.run()
return getStructure(curveModel)
}
async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
const { name, source, results, nbCurve } = ingredient
// TODO can these be added to the library?
if (name === 'HIV1_CAhex_0_1_0') return
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
if (name === 'iLDL') return
if (name === 'peptides') return
if (name === 'lypoglycane') return
if (source.pdb === 'None') return
const model = await getModel(source.pdb || name, baseUrl)
if (!model) return
if (nbCurve) {
return getCurve(name, getCurveTransforms(ingredient), model)
} else {
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
return getAssembly(getResultTransforms(results), structure)
}
}
export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
return Task.create('Create Packing Structure', async ctx => {
const { ingredients, name } = packing
const structures: Structure[] = []
for (const iName in ingredients) {
if (ctx.shouldUpdate) await ctx.update(iName)
const s = await getIngredientStructure(ingredients[iName], baseUrl)
if (s) structures.push(s)
}
if (ctx.shouldUpdate) await ctx.update(`${name} - units`)
const builder = Structure.Builder({ label: name })
let offsetInvariantId = 0
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`)
let maxInvariantId = 0
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, invariantId)
}
offsetInvariantId += maxInvariantId
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
const s = builder.getStructure()
return s
})
}
export const LoadCellPackModel = StateAction.build({
display: { name: 'Load CellPack Model' },
params: {
id: PD.Select('influenza_model1.json', [
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
['influenza_model1.json', 'influenza_model1'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
['curveTest', 'Curve Test'],
]),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('spacefill', [
['spacefill', 'Spacefill'],
['gaussian-surface', 'Gaussian Surface'],
['point', 'Point'],
] as ['spacefill' | 'gaussian-surface' | 'point', string][])
}, { isExpanded: true })
},
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
const url = getCellPackModelUrl(params.id, params.baseUrl)
const root = state.build().toRoot();
let cellPackBuilder: any
if (params.id === 'curveTest') {
const url = `${params.baseUrl}/extras/rna_allpoints.json`
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
const { points } = await (new Response(data)).json() as { points: number[] }
const curve0: Vec3[] = []
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j))
}
const cell: Cell = {
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
compartments: {
'CurveCompartment': {
interior: {
ingredients: {
'CurveIngredient': {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
}
}
}
}
}
}
cellPackBuilder = root
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
.apply(ParseCellPack)
} else {
cellPackBuilder = root
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack)
}
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
const { packings } = cellPackObject.data
const tree = state.build().to(cellPackBuilder.ref);
const isHiv = (
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
)
if (isHiv) {
for (let i = 0, il = packings.length; i < il; ++i) {
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
const url = `${params.baseUrl}/extras/rna_allpoints.json`
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
const { points } = await (new Response(data)).json() as { points: number[] }
const curve0: Vec3[] = []
for (let j = 0, jl = points.length; j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j))
}
packings[i].ingredients['RNA'] = {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
}
}
}
}
const colors = distinctColors(packings.length)
for (let i = 0, il = packings.length; i < il; ++i) {
const hcl = Hcl.fromColor(Hcl(), colors[i])
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
const p = { packing: i, baseUrl: params.baseUrl }
const expression = params.preset.traceOnly
? MS.struct.generator.atomGroups({
'atom-test': MS.core.logic.or([
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
])
})
: MS.struct.generator.all()
tree.apply(StructureFromCellpack, p)
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }, { state: { isGhost: true } })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
repr: getReprParams(ctx, params.preset),
color: getColorParams(hue)
})
)
}
if (isHiv) {
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
repr: getReprParams(ctx, params.preset),
color: UniformColorThemeProvider
})
)
}
console.time('cellpack')
await state.updateTree(tree).runInContext(taskCtx);
console.timeEnd('cellpack')
}));
function getReprParams(ctx: PluginContext, params: { representation: 'spacefill' | 'gaussian-surface' | 'point', traceOnly: boolean }) {
const { representation, traceOnly } = params
switch (representation) {
case 'spacefill':
return traceOnly
? [
ctx.structureRepresentation.registry.get('spacefill'),
() => ({ sizeFactor: 2, ignoreHydrogens: true })
] as [any, any]
: [
ctx.structureRepresentation.registry.get('spacefill'),
() => ({ ignoreHydrogens: true })
] as [any, any]
case 'gaussian-surface':
return [
ctx.structureRepresentation.registry.get('gaussian-surface'),
() => ({
quality: 'custom', resolution: 10, radiusOffset: 2,
alpha: 1.0, flatShaded: false, doubleSided: false,
ignoreHydrogens: true
})
] as [any, any]
case 'point':
return [
ctx.structureRepresentation.registry.get('point'),
() => ({ ignoreHydrogens: true })
] as [any, any]
}
}
function getColorParams(hue: [number, number]) {
return [
ModelIndexColorThemeProvider,
(c: ColorTheme.Provider<any>, ctx: ThemeRegistryContext) => {
return {
palette: {
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800,
maxCount: 75
}
}
}
}
] as [any, any]
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin/state/objects';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Task } from '../../../../mol-task';
import { CellPack as _CellPack, Cell, CellPacking } from './data';
import { createStructureFromCellPack } from './model';
// export const DefaultCellPackBaseUrl = 'https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/cellPACK_database_1.1.0/'
export const DefaultCellPackBaseUrl = 'https://mgldev.scripps.edu/projects/autoPACK/web/cellpackproject/'
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
export { ParseCellPack }
type ParseCellPack = typeof ParseCellPack
const ParseCellPack = PluginStateTransform.BuiltIn({
name: 'parse-cellpack',
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
from: PSO.Format.Json,
to: CellPack
})({
apply({ a }) {
return Task.create('Parse CellPack', async ctx => {
const cell = a.data as Cell
const packings: CellPacking[] = []
const { compartments, cytoplasme } = cell
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 })
}
}
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients })
return new CellPack({ cell, packings });
});
}
});
export { StructureFromCellpack }
type StructureFromCellpack = typeof ParseCellPack
const StructureFromCellpack = PluginStateTransform.BuiltIn({
name: 'structure-from-cellpack',
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
from: CellPack,
to: PSO.Molecule.Structure,
params: a => {
if (!a) {
return {
packing: PD.Numeric(0, {}, { description: 'Packing Index' }),
baseUrl: PD.Text(DefaultCellPackBaseUrl)
};
}
const options = a.data.packings.map((d, i) => [i, d.name] as [number, string])
return {
packing: PD.Select(0, options),
baseUrl: PD.Text(DefaultCellPackBaseUrl)
}
}
})({
apply({ a, params }) {
return Task.create('Structure from CellPack', async ctx => {
const packing = a.data.packings[params.packing]
const structure = await createStructureFromCellPack(packing, params.baseUrl).runInContext(ctx)
return new PSO.Molecule.Structure(structure, { label: packing.name })
});
}
});

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CIF } from '../../../../mol-io/reader/cif'
import { parsePDB } from '../../../../mol-io/reader/pdb/parser';
async function parseCif(data: string|Uint8Array) {
const comp = CIF.parse(data);
const parsed = await comp.run();
if (parsed.isError) throw parsed;
return parsed.result;
}
async function parsePDBfile(data: string, id: string) {
const comp = parsePDB(data, id);
const parsed = await comp.run();
if (parsed.isError) throw parsed;
return parsed.result;
}
async function downloadCif(url: string, isBinary: boolean) {
const data = await fetch(url);
return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
}
async function downloadPDB(url: string, id: string) {
const data = await fetch(url);
return parsePDBfile(await data.text(), id);
}
export async function getFromPdb(id: string) {
const parsed = await downloadCif(`https://files.rcsb.org/download/${id}.cif`, false);
return parsed.blocks[0];
}
function getCellPackDataUrl(id: string, baseUrl: string) {
const url = `${baseUrl}/other/${id}`
return url.endsWith('.pdb') ? url : `${url}.pdb`
}
export async function getFromCellPackDB(id: string, baseUrl: string) {
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl), name);
return parsed;
}

View File

@@ -16,7 +16,7 @@ import { PluginStateSnapshotManager } from '../../../mol-plugin/state/snapshots'
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
import { Text } from '../../../mol-geo/geometry/text/text';
import { UUID } from '../../../mol-util';
import { ColorNames } from '../../../mol-util/color/tables';
import { ColorNames } from '../../../mol-util/color/names';
import { Camera } from '../../../mol-canvas3d/camera';
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
@@ -93,32 +93,32 @@ function buildSnapshot(plugin: PluginContext, template: { tree: StateTree, struc
let i = 0;
for (const l of params.e.labels) {
const query = createQuery([l.i_atom]);
const expression = createExpression([l.i_atom]);
const group = b.to(template.structure)
.group(StateTransforms.Misc.CreateGroup, { label: `Label ${++i}` });
group
.apply(StateTransforms.Model.StructureSelection, { query, label: 'Atom' })
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: 'Atom' })
.apply(StateTransforms.Representation.StructureLabels3D, {
target: { name: 'static-text', params: { value: l.text || '' } },
options: labelOptions
});
group
.apply(StateTransforms.Model.StructureSelection, { query: MS.struct.modifier.wholeResidues([query]), label: 'Residue' })
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: MS.struct.modifier.wholeResidues([ expression ]), label: 'Residue' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
}
if (params.e.selected && params.e.selected.length > 0) {
b.to(template.structure)
.apply(StateTransforms.Model.StructureSelection, { query: createQuery(params.e.selected), label: `Selected` })
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: createExpression(params.e.selected), label: `Selected` })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick'));
}
// TODO
// for (const l of params.e.distances) {
// b.to('structure')
// .apply(StateTransforms.Model.StructureSelection, { query: createQuery([l.i_atom1, l.i_atom2]), label: `Distance ${++i}` })
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { query: createQuery([l.i_atom1, l.i_atom2]), label: `Distance ${++i}` })
// .apply(StateTransforms.Representation.StructureLabels3D, {
// target: { name: 'static-text', params: { value: l. || '' } },
// options: labelOptions
@@ -138,28 +138,24 @@ function buildSnapshot(plugin: PluginContext, template: { tree: StateTree, struc
}
function getCameraSnapshot(e: JoleculeSnapshot['camera']): Camera.Snapshot {
const direction = Vec3.sub(Vec3.zero(), e.pos, e.in);
const direction = Vec3.sub(Vec3(), e.pos, e.in);
Vec3.normalize(direction, direction);
const up = Vec3.sub(Vec3.zero(), e.pos, e.up);
const up = Vec3.sub(Vec3(), e.pos, e.up);
Vec3.normalize(up, up);
const s: Camera.Snapshot = {
mode: 'perspective',
position: Vec3.scaleAndAdd(Vec3.zero(), e.pos, direction, e.slab.zoom),
target: e.pos,
direction,
up,
near: e.slab.zoom + e.slab.z_front,
far: e.slab.zoom + e.slab.z_back,
fogNear: e.slab.zoom + e.slab.z_front,
fogFar: e.slab.zoom + e.slab.z_back,
fov: Math.PI / 4,
zoom: 1
position: Vec3.scaleAndAdd(Vec3(), e.pos, direction, e.slab.zoom),
target: e.pos,
radius: (e.slab.z_back - e.slab.z_front) / 2,
fog: 50,
up,
};
return s;
}
function createQuery(atomIndices: number[]) {
function createExpression(atomIndices: number[]) {
if (atomIndices.length === 0) return MS.struct.generator.empty();
return MS.struct.generator.atomGroups({

BIN
src/apps/viewer/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<title>Mol* Viewer</title>
<style>
* {

View File

@@ -2,14 +2,18 @@
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import './index.html'
import './favicon.ico'
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/command';
import { PluginSpec } from '../../mol-plugin/spec';
import { CreateJoleculeState } from './extensions/jolecule';
import { LoadCellPackModel } from './extensions/cellpack/model';
import { StructureFromCellpack } from './extensions/cellpack/state';
require('mol-plugin/skin/light.scss')
function getParam(name: string, regex: string): string {
@@ -21,7 +25,12 @@ const hideControls = getParam('hide-controls', `[^&]+`) === '1';
function init() {
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions, PluginSpec.Action(CreateJoleculeState)],
actions: [
...DefaultPluginSpec.actions,
PluginSpec.Action(CreateJoleculeState),
PluginSpec.Action(LoadCellPackModel),
PluginSpec.Action(StructureFromCellpack),
],
behaviors: [...DefaultPluginSpec.behaviors],
animations: [...DefaultPluginSpec.animations || []],
customParamEditors: DefaultPluginSpec.customParamEditors,
@@ -29,6 +38,9 @@ function init() {
initial: {
isExpanded: true,
showControls: !hideControls
},
controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls
}
}
};

View File

@@ -27,7 +27,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
name: 'proteopedia-wrapper-evolutionary-conservation',
display: 'Evolutionary Conservation',
async getData(model: Model) {
const id = model.label.toLowerCase();
const id = model.entryId.toLowerCase();
const req = await fetch(`https://proteopedia.org/cgi-bin/cnsrf?${id}`);
const json = await req.json();
const annotations = (json && json.residueAnnotations) || [];

View File

@@ -1,3 +1,18 @@
== v3.4 ==
* Fixed HET group reset.
* Updated core.
* Removed Camera Cliping.
== v3.3 ==
* Camera Clipping.
== v3.2 ==
* Fixed assembly loading.
* Better HET group focus.
== v3.0 ==
* Fixed initial camera zoom.

View File

@@ -54,7 +54,7 @@ export function createProteopediaCustomTheme(colors: number[]) {
const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color;
if (ctx.structure) {
const l = StructureElement.create()
const l = StructureElement.Location.create()
const { models } = ctx.structure
const asymIdSerialMap = new Map<string, number>()
for (let i = 0, il = models.length; i < il; ++i) {
@@ -67,7 +67,7 @@ export function createProteopediaCustomTheme(colors: number[]) {
}
color = (location: Location): Color => {
if (StructureElement.isLocation(location)) {
if (StructureElement.Location.is(location)) {
const asym_id = getAsymId(location.unit);
const o = asymIdSerialMap.get(asym_id(location)) || 0;
return colors[o % colorCount].color;

View File

@@ -18,9 +18,9 @@ export interface ModelInfo {
export namespace ModelInfo {
async function getPreferredAssembly(ctx: PluginContext, model: Model) {
if (model.label.length <= 3) return void 0;
if (model.entryId.length <= 3) return void 0;
try {
const id = model.label.toLowerCase();
const id = model.entryId.toLowerCase();
const src = await ctx.runTask(ctx.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/${id}` })) as string;
const json = JSON.parse(src);
const data = json && json[id];
@@ -105,6 +105,8 @@ export enum StateElements {
ModelProps = 'model-props',
Assembly = 'assembly',
VolumeStreaming = 'volume-streaming',
Sequence = 'sequence',
SequenceVisual = 'sequence-visual',
Het = 'het',
@@ -113,5 +115,6 @@ export enum StateElements {
Water = 'water',
WaterVisual = 'water-visual',
HetGroupFocus = 'het-group-focus'
HetGroupFocus = 'het-group-focus',
HetGroupFocusGroup = 'het-group-focus-group'
}

View File

@@ -40,6 +40,13 @@
width: 100%;
display: block;
}
#volume-streaming-wrapper {
position: absolute;
top: 100px;
left: 780px;
width: 300px;
}
</style>
<link rel="stylesheet" type="text/css" href="app.css" />
<script type="text/javascript" src="./index.js"></script>
@@ -55,6 +62,7 @@
</select>
</div>
<div id="app"></div>
<div id="volume-streaming-wrapper"></div>
<script>
// it might be a good idea to define these colors in a separate script file
var CustomColors = [0x00ff00, 0x0000ff];
@@ -66,7 +74,7 @@
function $(id) { return document.getElementById(id); }
var pdbId = '1eve', assemblyId= 'preferred';
var pdbId = '1cbs', assemblyId= 'preferred';
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
var format = 'cif';
@@ -122,7 +130,11 @@
addSeparator();
addHeader('Camera');
addControl('Toggle Spin', () => PluginWrapper.toggleSpin());
addControl('Reset Position', () => PluginWrapper.camera.resetPosition());
addControl('Toggle Spin', () => PluginWrapper.camera.toggleSpin());
// Same as "wheel icon" and Viewport options
// addControl('Clip', () => PluginWrapper.viewport.setSettings({ clip: [33, 66] }));
// addControl('Reset Clip', () => PluginWrapper.viewport.setSettings({ clip: [1, 100] }));
addSeparator();
@@ -141,7 +153,8 @@
addSeparator();
addHeader('Misc');
addControl('Apply Evo Cons', () => PluginWrapper.coloring.evolutionaryConservation());
addControl('Apply Evo Cons Style', () => PluginWrapper.coloring.evolutionaryConservation());
addControl('Apply Evo Cons Colors', () => PluginWrapper.coloring.evolutionaryConservation({ sequence: true, het: false, keepStyle: true }));
addControl('Default Visuals', () => PluginWrapper.updateStyle());
addSeparator();
@@ -151,6 +164,11 @@
addHetGroupsContainer();
addSeparator();
addHeader('Exp. Data');
addControl('Init', () => PluginWrapper.experimentalData.init($('volume-streaming-wrapper')));
addControl('Remove', () => PluginWrapper.experimentalData.remove());
addSeparator();
addHeader('State');
var snapshot;

View File

@@ -4,6 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ReactDOM from 'react-dom';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import './index.html'
import { PluginContext } from '../../mol-plugin/context';
@@ -13,11 +14,11 @@ import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transfo
import { Color } from '../../mol-util/color';
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
import { StateBuilder, StateObject } from '../../mol-state';
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
import { EvolutionaryConservation } from './annotation';
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
import { RxEventHelper } from '../../mol-util/rx-event-helper';
import { ControlsWrapper } from './ui/controls';
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
import { PluginState } from '../../mol-plugin/state';
import { Scheduler } from '../../mol-task';
import { createProteopediaCustomTheme } from './coloring';
@@ -25,7 +26,9 @@ import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
import { BuiltInColorThemes } from '../../mol-theme/color';
import { BuiltInSizeThemes } from '../../mol-theme/size';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
// import { Vec3 } from 'mol-math/linear-algebra';
// import { ParamDefinition } from 'mol-util/param-definition';
// import { Text } from 'mol-geo/geometry/text/text';
@@ -33,7 +36,7 @@ require('../../mol-plugin/skin/light.scss')
class MolStarProteopediaWrapper {
static VERSION_MAJOR = 3;
static VERSION_MINOR = 1;
static VERSION_MINOR = 4;
private _ev = RxEventHelper.create();
@@ -78,7 +81,7 @@ class MolStarProteopediaWrapper {
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
}
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
@@ -187,7 +190,7 @@ class MolStarProteopediaWrapper {
}
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
async load({ url, format = 'cif', assemblyId = '', representationStyle }: LoadParams) {
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
let loadType: 'full' | 'update' = 'full';
const state = this.plugin.state.dataState;
@@ -200,14 +203,17 @@ class MolStarProteopediaWrapper {
if (loadType === 'full') {
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
const modelTree = this.model(this.download(state.build().toRoot(), url), format, assemblyId);
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
await this.applyState(modelTree);
const info = await this.doInfo(true);
const structureTree = this.structure((assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId);
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
const structureTree = this.structure(asmId);
await this.applyState(structureTree);
} else {
const tree = state.build();
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
const info = await this.doInfo(true);
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
await this.applyState(tree);
}
@@ -235,6 +241,38 @@ class MolStarProteopediaWrapper {
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
}
viewport = {
setSettings: (settings?: Canvas3DProps) => {
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
settings: settings || DefaultCanvas3DParams
});
}
};
camera = {
toggleSpin: () => this.toggleSpin(),
resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
// if (!options) {
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
// settings: {
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
// clip: DefaultCanvas3DParams.clip
// }
// });
// return;
// }
// options = options || { };
// const props = this.plugin.canvas3d.props;
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
// });
// }
}
animate = {
modelIndex: {
maxFPS: 8,
@@ -247,53 +285,85 @@ class MolStarProteopediaWrapper {
}
coloring = {
evolutionaryConservation: async () => {
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
evolutionaryConservation: async (params?: { sequence?: boolean, het?: boolean, keepStyle?: boolean }) => {
if (!params || !params.keepStyle) {
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
}
const state = this.state;
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
// for (const v of visuals) {
// }
const tree = state.build();
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
// for (const v of visuals) {
// }
if (!params || !!params.sequence) {
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
}
if (params && !!params.het) {
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
}
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
}
}
private experimentalDataElement?: Element = void 0;
experimentalData = {
init: async (parent: Element) => {
const asm = this.state.select(StateElements.Assembly)[0].obj!;
const params = InitVolumeStreaming.createDefaultParams(asm, this.plugin);
params.options.behaviorRef = StateElements.VolumeStreaming;
params.defaultView = 'box';
params.options.channelParams['fo-fc(+ve)'] = { wireframe: true };
params.options.channelParams['fo-fc(-ve)'] = { wireframe: true };
await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
this.experimentalDataElement = parent;
volumeStreamingControls(this.plugin, parent);
},
remove: () => {
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
if (!r) return;
PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
if (this.experimentalDataElement) {
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
this.experimentalDataElement = void 0;
}
}
}
hetGroups = {
reset: () => {
const update = this.state.build().delete(StateElements.HetGroupFocus);
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
},
focusFirst: async (resn: string) => {
focusFirst: async (compId: string) => {
if (!this.state.transforms.has(StateElements.Assembly)) return;
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
const update = this.state.build();
update.delete(StateElements.HetGroupFocus);
update.delete(StateElements.HetGroupFocusGroup);
const surroundings = MS.struct.modifier.includeSurroundings({
0: MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), resn]),
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
})
]),
radius: 5,
'as-whole-residues': true
});
const core = MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), compId]),
'group-by': MS.core.str.concat([MS.struct.atomProperty.core.operatorName(), MS.struct.atomProperty.macromolecular.residueKey()])
})
]);
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
const sel = update.to(StateElements.Assembly)
.apply(StateTransforms.Model.StructureSelection, { label: resn, query: surroundings }, { ref: StateElements.HetGroupFocus });
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
sel.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus })
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings', expression: surroundings })
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
// sel.apply(StateTransforms.Representation.StructureLabels3D, {
// target: { name: 'residues', params: { } },
// options: {
@@ -313,7 +383,7 @@ class MolStarProteopediaWrapper {
// const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
// Vec3.normalize(position, position);
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, 0.75 * sphere.radius);
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, Math.max(sphere.radius, 5));
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
}
}
@@ -327,6 +397,15 @@ class MolStarProteopediaWrapper {
});
}
private createCoreVisualParams() {
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
repr: BuiltInStructureRepresentations['ball-and-stick'],
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
});
}
snapshot = {
get: () => {
return this.plugin.state.getSnapshot();

View File

@@ -5,10 +5,14 @@
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { PluginUIComponent } from '../../../mol-plugin/ui/base';
import { CurrentObject } from '../../../mol-plugin/ui/plugin';
import { CurrentObject, PluginContextContainer } from '../../../mol-plugin/ui/plugin';
import { AnimationControls } from '../../../mol-plugin/ui/state/animation';
import { CameraSnapshots } from '../../../mol-plugin/ui/camera';
import { PluginContext } from '../../../mol-plugin/context';
import { TransformUpdaterControl } from '../../../mol-plugin/ui/state/update-transform';
import { StateElements } from '../helpers';
export class ControlsWrapper extends PluginUIComponent {
render() {
@@ -18,4 +22,11 @@ export class ControlsWrapper extends PluginUIComponent {
<CameraSnapshots />
</div>;
}
}
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={plugin}>
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
</PluginContextContainer>,
parent);
}

View File

@@ -1,56 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ColorTheme } from '../../mol-theme/color';
import { Color } from '../../mol-util/color';
export interface ColorThemeComponentProps {
colorTheme: ColorTheme<any>
}
export interface ColorThemeComponentState {
}
export class ColorThemeComponent extends React.Component<ColorThemeComponentProps, ColorThemeComponentState> {
state = {
}
render() {
const ct = this.props.colorTheme
return <div>
<span>Color Theme Info </span>
{ct.description ? <div><i>{ct.description}</i></div> : ''}
{
ct.legend && ct.legend.kind === 'scale-legend'
? <div
style={{
width: '100%',
height: '30px',
background: `linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`
}}
>
<span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.minLabel}</span>
<span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.maxLabel}</span>
</div>
: ct.legend && ct.legend.kind === 'table-legend'
? <div>
{ct.legend.table.map((value, i) => {
const [name, color] = value
return <div key={i} style={{minWidth: '60px', marginRight: '5px', display: 'inline-block'}}>
<div style={{width: '30px', height: '20px', backgroundColor: Color.toStyle(color), display: 'inline-block'}}></div>
{name}
</div>
})}
</div>
: ''
}
</div>;
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface BooleanParamComponentProps {
label: string
param: PD.Boolean
value: boolean
onChange(v: boolean): void
}
export interface BooleanParamComponentState {
value: boolean
}
export class BooleanParamComponent extends React.Component<BooleanParamComponentProps, BooleanParamComponentState> {
state = {
value: this.props.value
}
onChange(value: boolean) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<button onClick={e => this.onChange(!this.state.value) }>
{this.state.value ? 'Off' : 'On'}
</button>
</div>;
}
}

View File

@@ -1,43 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { ColorNames } from '../../../mol-util/color/tables';
import { Color } from '../../../mol-util/color';
export interface ColorParamComponentProps {
label: string
param: PD.Color
value: Color
onChange(v: Color): void
}
export interface ColorParamComponentState {
value: Color
}
export class ColorParamComponent extends React.Component<ColorParamComponentProps, ColorParamComponentState> {
state = {
value: this.props.value
}
onChange(value: Color) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<select value={this.state.value} onChange={e => this.onChange(Color(parseInt(e.target.value))) }>
{Object.keys(ColorNames).map(name => {
return <option key={name} value={(ColorNames as { [k: string]: Color})[name]}>{name}</option>
})}
</select>
</div>;
}
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface MultiSelectParamComponentProps<T extends string> {
label: string
param: PD.MultiSelect<T>
value: T[]
onChange(v: T[]): void
}
export interface MultiSelectParamComponentState<T extends string> {
value: T[]
}
export class MultiSelectParamComponent<T extends string> extends React.Component<MultiSelectParamComponentProps<T>, MultiSelectParamComponentState<T>> {
state = {
value: this.props.value
}
onChange(value: T[]) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<select multiple value={this.state.value} onChange={e => {
const value = Array.from(e.target.options).filter(option => option.selected).map(option => option.value)
this.onChange(value as T[])
}}>
{this.props.param.options.map(v => {
const [value, label] = v
return <option key={label} value={value}>{label}</option>
})}
</select>
</div>;
}
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface NumberParamComponentProps {
label: string
param: PD.Numeric
value: number
onChange(v: number): void
}
export interface NumberParamComponentState {
value: number
}
export class NumberParamComponent extends React.Component<NumberParamComponentProps, NumberParamComponentState> {
state = {
value: this.props.value
}
onChange(valueStr: string) {
const value = this.props.param.step && Number.isInteger(this.props.param.step) ? parseInt(valueStr) : parseFloat(valueStr)
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<input type='range'
value={this.state.value}
min={this.props.param.min}
max={this.props.param.max}
step={this.props.param.step}
onChange={e => this.onChange(e.currentTarget.value)}
>
</input>
</div>;
}
}

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface SelectParamComponentProps<T extends string> {
label: string
param: PD.Select<T>
value: T
onChange(v: T): void
}
export interface SelectParamComponentState<T extends string> {
value: T
}
export class SelectParamComponent<T extends string> extends React.Component<SelectParamComponentProps<T>, SelectParamComponentState<T>> {
state = {
value: this.props.value
}
onChange(value: T) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<select value={this.state.value} onChange={e => this.onChange(e.target.value as T) }>
{this.props.param.options.map(v => {
const [value, label] = v
return <option key={label} value={value}>{label}</option>
})}
</select>
</div>;
}
}

View File

@@ -1,41 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface TextParamComponentProps {
label: string
param: PD.Text
value: string
onChange(v: string): void
}
export interface TextParamComponentState {
value: string
}
export class TextParamComponent extends React.Component<TextParamComponentProps, TextParamComponentState> {
state = {
value: this.props.value
}
onChange(value: string) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<input type='text'
value={this.state.value}
onChange={e => this.onChange(e.currentTarget.value)}
>
</input>
</div>;
}
}

View File

@@ -1,64 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { BooleanParamComponent } from './parameter/boolean';
import { NumberParamComponent } from './parameter/number';
import { SelectParamComponent } from './parameter/select';
import { MultiSelectParamComponent } from './parameter/multi-select';
import { TextParamComponent } from './parameter/text';
import { ColorParamComponent } from './parameter/color';
import { camelCaseToWords } from '../../mol-util/string';
interface ParametersProps<P extends PD.Params> {
params: P
values: { [k in keyof P]: P[k]['defaultValue'] }
onChange<K extends keyof P>(k: K, v: P[K]['defaultValue']): void
}
type ParametersState = {}
function getParamComponent<P extends PD.Any>(label: string, param: PD.Any, value: P['defaultValue'], onChange: (v: P['defaultValue']) => void) {
switch (param.type) {
case 'boolean':
return <BooleanParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'number':
return <NumberParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'select':
return <SelectParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'multi-select':
return <MultiSelectParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'text':
return <TextParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'color':
return <ColorParamComponent label={label} param={param} value={value} onChange={onChange} />
}
return ''
}
function getLabel(name: string, param: PD.Base<any>) {
return param.label === undefined ? camelCaseToWords(name) : param.label
}
export class ParametersComponent<P extends PD.Params> extends React.Component<ParametersProps<P>, ParametersState> {
onChange(k: string, value: any) {
this.props.onChange(k, value)
}
render() {
return <div style={{ width: '100%' }}>
{ Object.keys(this.props.params).map(k => {
const param = this.props.params[k]
const value = this.props.values[k]
const label = getLabel(k, param)
return <div key={k}>
{getParamComponent(label, param, value, v => this.onChange(k, v))}
</div>
})}
</div>;
}
}

View File

@@ -1,81 +0,0 @@
// /**
// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
// *
// * @author David Sehnal <david.sehnal@gmail.com>
// */
// import * as React from 'react'
// import { Structure, StructureSequence, Queries, StructureSelection, StructureProperties, StructureQuery } from 'mol-model/structure';
// import { EmptyLoci } from 'mol-model/loci';
// export class SequenceView extends View<SequenceViewController, {}, {}> {
// render() {
// const s = this.controller.latestState.structure;
// if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
// const seqs = s.models[0].sequence.sequences;
// return <div className='molstar-sequence-view-wrap'>
// {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
// </div>;
// }
// }
// function createQuery(entityId: string, label_seq_id: number) {
// return Queries.generators.atoms({
// entityTest: ctx => StructureProperties.entity.id(ctx.element) === entityId,
// residueTest: ctx => StructureProperties.residue.label_seq_id(ctx.element) === label_seq_id
// });
// }
// // TODO: this is really ineffective and should be done using a canvas.
// class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
// raiseInteractityEvent(seqId?: number) {
// if (typeof seqId === 'undefined') {
// InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
// return;
// }
// const query = createQuery(this.props.seq.entityId, seqId);
// const loci = StructureSelection.toLoci(StructureQuery.run(query, this.props.structure));
// if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
// else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci);
// }
// render() {
// const { ctx, seq } = this.props;
// const { offset, sequence } = seq.sequence;
// const elems: JSX.Element[] = [];
// for (let i = 0, _i = sequence.length; i < _i; i++) {
// elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
// }
// return <div style={{ wordWrap: 'break-word' }}>
// <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset}&nbsp;</span>
// {elems}
// </div>;
// }
// }
// class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
// state = { isHighlighted: false }
// mouseEnter = () => {
// this.setState({ isHighlighted: true });
// this.props.parent.raiseInteractityEvent(this.props.seqId);
// }
// mouseLeave = () => {
// this.setState({ isHighlighted: false });
// this.props.parent.raiseInteractityEvent();
// }
// render() {
// return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
// style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
// {this.props.letter}
// </span>;
// }
// }

View File

@@ -7,17 +7,11 @@
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { Object3D } from '../mol-gl/object3d';
import { BehaviorSubject } from 'rxjs';
import { CameraTransitionManager } from './camera/transition';
export { Camera }
// TODO: slab controls that modify near/far planes?
class Camera implements Object3D {
readonly updatedViewProjection = new BehaviorSubject<Camera>(this);
class Camera {
readonly view: Mat4 = Mat4.identity();
readonly projection: Mat4 = Mat4.identity();
readonly projectionView: Mat4 = Mat4.identity();
@@ -32,14 +26,17 @@ class Camera implements Object3D {
width: 1, height: 1
}
near = 1
far = 10000
fogNear = 5000
fogFar = 10000
zoom = 1
readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
get position() { return this.state.position; }
set position(v: Vec3) { Vec3.copy(this.state.position, v); }
get direction() { return this.state.direction; }
set direction(v: Vec3) { Vec3.copy(this.state.direction, v); }
get up() { return this.state.up; }
set up(v: Vec3) { Vec3.copy(this.state.up, v); }
@@ -51,10 +48,12 @@ class Camera implements Object3D {
private deltaDirection = Vec3.zero();
private newPosition = Vec3.zero();
updateMatrices() {
update() {
const snapshot = this.state as Camera.Snapshot;
const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target);
snapshot.zoom = this.viewport.height / height;
this.zoom = this.viewport.height / height;
updateClip(this);
switch (this.state.mode) {
case 'orthographic': updateOrtho(this); break;
@@ -62,11 +61,7 @@ class Camera implements Object3D {
default: throw new Error('unknown camera mode');
}
const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON.Value) || !Mat4.areEqual(this.view, this.prevView, EPSILON.Value);
Mat4.mul(this.projectionView, this.projection, this.view)
Mat4.invert(this.inverseProjectionView, this.projectionView)
const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON) || !Mat4.areEqual(this.view, this.prevView, EPSILON);
if (changed) {
Mat4.mul(this.projectionView, this.projection, this.view)
@@ -74,14 +69,13 @@ class Camera implements Object3D {
Mat4.copy(this.prevView, this.view);
Mat4.copy(this.prevProjection, this.projection);
this.updatedViewProjection.next(this);
}
return changed;
}
setState(snapshot: Partial<Camera.Snapshot>) {
this.transition.apply(snapshot);
setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
this.transition.apply(snapshot, durationMs);
}
getSnapshot() {
@@ -95,31 +89,24 @@ class Camera implements Object3D {
const { width, height } = this.viewport
const aspect = width / height
const aspectFactor = (height < width ? 1 : aspect)
const currentDistance = Vec3.distance(this.state.position, target)
const targetDistance = Math.abs((radius / aspectFactor) / Math.sin(fov / 2))
const deltaDistance = Math.abs(currentDistance - targetDistance)
Vec3.sub(this.deltaDirection, this.state.position, target)
Vec3.setMagnitude(this.deltaDirection, this.state.direction, deltaDistance)
if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection)
Vec3.add(this.newPosition, this.state.position, this.deltaDirection)
Vec3.sub(this.deltaDirection, this.target, this.position)
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance)
Vec3.sub(this.newPosition, target, this.deltaDirection)
return { target, position: Vec3.clone(this.newPosition) };
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
state.target = Vec3.clone(target)
state.radius = radius
state.position = Vec3.clone(this.newPosition)
return state
}
focus(target: Vec3, radius: number) {
this.setState(this.getFocus(target, radius));
focus(target: Vec3, radius: number, durationMs?: number) {
if (radius > 0) this.setState(this.getFocus(target, radius), durationMs);
}
// lookAt(target: Vec3) {
// cameraLookAt(this.position, this.up, this.direction, target);
// }
// translate(v: Vec3) {
// Vec3.add(this.position, this.position, v);
// cameraLookAt(this.position, this.up, this.direction, this.target);
// }
project(out: Vec4, point: Vec3) {
return cameraProject(out, point, this.viewport, this.projectionView)
}
@@ -128,10 +115,6 @@ class Camera implements Object3D {
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView)
}
dispose() {
this.updatedViewProjection.complete();
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
this.viewport = viewport;
Camera.copySnapshot(this.state, state);
@@ -142,13 +125,6 @@ class Camera implements Object3D {
namespace Camera {
export type Mode = 'perspective' | 'orthographic'
export interface ClippingInfo {
near: number,
far: number,
fogNear: number,
fogFar: number
}
/**
* Sets an offseted view in a larger frustum. This is useful for
* - multi-window or multi-monitor/multi-machine setups
@@ -176,64 +152,48 @@ namespace Camera {
export function createDefaultSnapshot(): Snapshot {
return {
mode: 'perspective',
fov: Math.PI / 4,
position: Vec3.zero(),
direction: Vec3.create(0, 0, -1),
position: Vec3.create(0, 0, 100),
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
near: 1,
far: 10000,
fogNear: 1,
fogFar: 10000,
fov: Math.PI / 4,
zoom: 1,
radius: 10,
fog: 50,
};
}
export interface Snapshot {
mode: Mode,
mode: Mode
fov: number
position: Vec3,
// Normalized camera direction
direction: Vec3,
up: Vec3,
target: Vec3,
position: Vec3
up: Vec3
target: Vec3
near: number,
far: number,
fogNear: number,
fogFar: number,
fov: number,
zoom: number,
radius: number
fog: number
}
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
if (!source) return;
if (!source) return out;
if (typeof source.mode !== 'undefined') out.mode = source.mode;
if (typeof source.fov !== 'undefined') out.fov = source.fov;
if (typeof source.position !== 'undefined') Vec3.copy(out.position, source.position);
if (typeof source.direction !== 'undefined') Vec3.copy(out.direction, source.direction);
if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up);
if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target);
if (typeof source.near !== 'undefined') out.near = source.near;
if (typeof source.far !== 'undefined') out.far = source.far;
if (typeof source.fogNear !== 'undefined') out.fogNear = source.fogNear;
if (typeof source.fogFar !== 'undefined') out.fogFar = source.fogFar;
if (typeof source.radius !== 'undefined') out.radius = source.radius;
if (typeof source.fog !== 'undefined') out.fog = source.fog;
if (typeof source.fov !== 'undefined') out.fov = source.fov;
if (typeof source.zoom !== 'undefined') out.zoom = source.zoom;
return out;
}
}
const _center = Vec3.zero();
function updateOrtho(camera: Camera) {
const { viewport, state: { zoom, near, far }, viewOffset } = camera
const { viewport, zoom, near, far, viewOffset } = camera
const fullLeft = -(viewport.width - viewport.x) / 2
const fullRight = (viewport.width - viewport.x) / 2
@@ -250,7 +210,7 @@ function updateOrtho(camera: Camera) {
let top = cy + dy
let bottom = cy - dy
if (viewOffset && viewOffset.enabled) {
if (viewOffset.enabled) {
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth)
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight)
const scaleW = (fullRight - fullLeft) / viewOffset.width
@@ -265,21 +225,20 @@ function updateOrtho(camera: Camera) {
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
// build view matrix
Vec3.add(_center, camera.position, camera.direction)
Mat4.lookAt(camera.view, camera.position, _center, camera.up)
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
}
function updatePers(camera: Camera) {
const aspect = camera.viewport.width / camera.viewport.height
const { state: { fov, near, far }, viewOffset } = camera
const { near, far, viewOffset } = camera
let top = near * Math.tan(0.5 * fov)
let top = near * Math.tan(0.5 * camera.state.fov)
let height = 2 * top
let width = aspect * height
let left = -0.5 * width
if (viewOffset && viewOffset.enabled) {
if (viewOffset.enabled) {
left += viewOffset.offsetX * width / viewOffset.fullWidth
top -= viewOffset.offsetY * height / viewOffset.fullHeight
width *= viewOffset.width / viewOffset.fullWidth
@@ -290,6 +249,33 @@ function updatePers(camera: Camera) {
Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far)
// build view matrix
Vec3.add(_center, camera.position, camera.direction)
Mat4.lookAt(camera.view, camera.position, _center, camera.up)
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
}
function updateClip(camera: Camera) {
const { radius, mode, fog } = camera.state
const cDist = Vec3.distance(camera.position, camera.target)
const bRadius = Math.max(1, radius)
let near = cDist - bRadius
let far = cDist + bRadius
const fogNearFactor = -(50 - fog) / 50
let fogNear = cDist - (bRadius * fogNearFactor)
let fogFar = cDist + bRadius
if (mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(5, near)
far = Math.max(5, far)
} else {
near = Math.max(0, near)
far = Math.max(0, far)
}
camera.near = near;
camera.far = far;
camera.fogNear = fogNear;
camera.fogFar = fogFar;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -22,7 +22,7 @@ class CameraTransitionManager {
private current = Camera.createDefaultSnapshot();
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
if (durationMs <= 0 || to.mode !== this.camera.state.mode) {
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(to);
return;
}
@@ -68,33 +68,21 @@ class CameraTransitionManager {
namespace CameraTransitionManager {
export type TransitionFunc = (out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot) => void
const _rot = Quat.identity();
export function defaultTransition(out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot): void {
Camera.copySnapshot(out, target);
// Rotate direction
const rot = Quat.identity();
Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.direction, target.direction), t);
Vec3.transformQuat(out.direction, source.direction, rot);
// Rotate up
Quat.setIdentity(rot);
Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.up, target.up), t);
Vec3.transformQuat(out.up, source.up, rot);
Quat.slerp(_rot, Quat.Identity, Quat.rotationTo(_rot, source.up, target.up), t);
Vec3.transformQuat(out.up, source.up, _rot);
// Lerp target
// Lerp target, position & radius
Vec3.lerp(out.target, source.target, target.target, t);
Vec3.lerp(out.position, source.position, target.position, t);
out.radius = lerp(source.radius, target.radius, t);
// Update position
const dist = -lerp(Vec3.distance(source.position, source.target), Vec3.distance(target.position, target.target), t);
Vec3.scale(out.position, out.direction, dist);
Vec3.add(out.position, out.position, out.target);
// Lerp other props
out.zoom = lerp(source.zoom, target.zoom, t);
// Lerp fov & fog
out.fov = lerp(source.fov, target.fov, t);
out.near = lerp(source.near, target.near, t);
out.far = lerp(source.far, target.far, t);
out.fogNear = lerp(source.fogNear, target.fogNear, t);
out.fogFar = lerp(source.fogFar, target.fogFar, t);
out.fog = lerp(source.fog, target.fog, t);
}
}

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3, Vec4, EPSILON } from '../../mol-math/linear-algebra'
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra'
export { Viewport }
@@ -55,31 +55,6 @@ namespace Viewport {
//
const tmpVec3 = Vec3()
/** Modifies the direction & up vectors in place, both are normalized */
export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target: Vec3) {
Vec3.sub(tmpVec3, target, position)
Vec3.normalize(tmpVec3, tmpVec3)
if (!Vec3.isZero(tmpVec3)) {
// change direction vector to look at target
const d = Vec3.dot(tmpVec3, up)
if (Math.abs(d - 1) < EPSILON.Value) { // parallel
Vec3.scale(up, direction, -1)
} else if (Math.abs(d + 1) < EPSILON.Value) { // anti parallel
Vec3.copy(up, direction)
}
Vec3.copy(direction, tmpVec3)
// normalize up vector
Vec3.cross(tmpVec3, direction, up)
Vec3.normalize(tmpVec3, tmpVec3)
Vec3.cross(up, tmpVec3, direction)
Vec3.normalize(up, up)
}
}
const NEAR_RANGE = 0
const FAR_RANGE = 1

View File

@@ -17,7 +17,7 @@ import { Representation } from '../mol-repr/representation';
import Scene from '../mol-gl/scene';
import { GraphicsRenderVariant } from '../mol-gl/webgl/render-item';
import { PickingId } from '../mol-geo/geometry/picking';
import { MarkerAction } from '../mol-geo/geometry/marker-data';
import { MarkerAction } from '../mol-util/marker-action';
import { Loci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
import { Camera } from './camera';
import { ParamDefinition as PD } from '../mol-util/param-definition';
@@ -31,14 +31,13 @@ import { PixelData } from '../mol-util/image';
import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { Task } from '../mol-task';
import { ImagePass, ImageProps } from './passes/image';
export const Canvas3DParams = {
// TODO: FPS cap?
// maxFps: PD.Numeric(30),
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
cameraClipDistance: PD.Numeric(0, { min: 0.0, max: 50.0, step: 0.1 }, { description: 'The distance between camera and scene at which to clip regardless of near clipping plane.' }),
clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
@@ -46,6 +45,7 @@ export const Canvas3DParams = {
trackball: PD.Group(TrackballControlsParams),
debug: PD.Group(DebugHelperParams)
}
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
export { Canvas3D }
@@ -66,6 +66,7 @@ interface Canvas3D {
getLoci: (pickingId: PickingId) => Representation.Loci
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
handleResize: () => void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
@@ -74,6 +75,7 @@ interface Canvas3D {
downloadScreenshot: () => void
getPixelData: (variant: GraphicsRenderVariant) => PixelData
setProps: (props: Partial<Canvas3DProps>) => void
getImagePass: () => ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
@@ -85,25 +87,27 @@ interface Canvas3D {
}
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
const DefaultRunTask = (task: Task<unknown>) => task.run()
namespace Canvas3D {
export interface HighlightEvent { current: Representation.Loci, prev: Representation.Loci, modifiers?: ModifiersKeys }
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
const gl = getGLContext(canvas, {
alpha: false,
alpha: true,
antialias: true,
depth: true,
preserveDrawingBuffer: true
preserveDrawingBuffer: true,
premultipliedAlpha: false,
})
if (gl === null) throw new Error('Could not create a WebGL rendering context')
const input = InputObserver.fromElement(canvas)
return Canvas3D.create(gl, input, props)
return Canvas3D.create(gl, input, props, runTask)
}
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p = { ...PD.getDefaultValues(Canvas3DParams), ...props }
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
const p = { ...DefaultCanvas3DParams, ...props }
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>()
@@ -112,31 +116,31 @@ namespace Canvas3D {
const startTime = now()
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
const camera = new Camera({
near: 0.1,
far: 10000,
position: Vec3.create(0, 0, 10),
mode: p.cameraMode
})
const webgl = createContext(gl)
let width = gl.drawingBufferWidth
let height = gl.drawingBufferHeight
const scene = Scene.create(webgl)
const camera = new Camera({
position: Vec3.create(0, 0, 100),
mode: p.cameraMode,
fog: p.cameraFog
})
const controls = TrackballControls.create(input, camera, p.trackball)
const renderer = Renderer.create(webgl, camera, p.renderer)
const renderer = Renderer.create(webgl, p.renderer)
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
const drawPass = new DrawPass(webgl, renderer, scene, debugHelper)
const pickPass = new PickPass(webgl, renderer, scene, 0.5)
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
let isUpdating = false
let drawPending = false
let cameraResetRequested = false
function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci
@@ -144,7 +148,9 @@ namespace Canvas3D {
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId)
if (!isEmptyLoci(_loci)) {
if (!isEmptyLoci(loci)) console.warn('found another loci')
if (!isEmptyLoci(loci)) {
console.warn('found another loci, this should not happen')
}
loci = _loci
repr = _repr
}
@@ -168,46 +174,13 @@ namespace Canvas3D {
}
}
let currentNear = -1, currentFar = -1, currentFogNear = -1, currentFogFar = -1
function setClipping() {
const cDist = Vec3.distance(camera.state.position, camera.state.target)
const bRadius = Math.max(10, scene.boundingSphere.radius)
const nearFactor = (50 - p.clip[0]) / 50
const farFactor = -(50 - p.clip[1]) / 50
let near = cDist - (bRadius * nearFactor)
let far = cDist + (bRadius * farFactor)
const fogNearFactor = (50 - p.fog[0]) / 50
const fogFarFactor = -(50 - p.fog[1]) / 50
let fogNear = cDist - (bRadius * fogNearFactor)
let fogFar = cDist + (bRadius * fogFarFactor)
if (camera.state.mode === 'perspective') {
near = Math.max(1, p.cameraClipDistance, near)
far = Math.max(1, far)
fogNear = Math.max(1, fogNear)
fogFar = Math.max(1, fogFar)
} else if (camera.state.mode === 'orthographic') {
if (p.cameraClipDistance > 0) {
near = Math.max(p.cameraClipDistance, near)
}
}
if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
camera.setState({ near, far, fogNear, fogFar })
currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
}
}
function render(variant: 'pick' | 'draw', force: boolean) {
if (isUpdating) return false
if (scene.isCommiting) return false
let didRender = false
controls.update(currentTime);
// TODO: is this a good fix? Also, setClipping does not work if the user has manually set a clipping plane.
if (!camera.transition.inTransition) setClipping();
const cameraChanged = camera.updateMatrices();
controls.update(currentTime)
Viewport.set(camera.viewport, 0, 0, width, height)
const cameraChanged = camera.update()
multiSample.update(force || cameraChanged, currentTime)
if (force || cameraChanged || multiSample.enabled) {
@@ -216,9 +189,9 @@ namespace Canvas3D {
pickPass.render()
break;
case 'draw':
renderer.setViewport(0, 0, width, height);
renderer.setViewport(0, 0, width, height)
if (multiSample.enabled) {
multiSample.render()
multiSample.render(true)
} else {
drawPass.render(!postprocessing.enabled)
if (postprocessing.enabled) postprocessing.render(true)
@@ -261,8 +234,21 @@ namespace Canvas3D {
return pickPass.identify(x, y)
}
function commit(renderObjects?: readonly GraphicsRenderObject[]) {
scene.update(renderObjects, false)
runTask(scene.commit()).then(() => {
if (cameraResetRequested && !scene.isCommiting) {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
cameraResetRequested = false
}
if (debugHelper.isEnabled) debugHelper.update()
requestDraw(true)
reprCount.next(reprRenderObjects.size)
})
}
function add(repr: Representation.Any) {
isUpdating = true
const oldRO = reprRenderObjects.get(repr)
const newRO = new Set<GraphicsRenderObject>()
repr.renderObjects.forEach(o => newRO.add(o))
@@ -276,11 +262,7 @@ namespace Canvas3D {
repr.renderObjects.forEach(o => scene.add(o))
}
reprRenderObjects.set(repr, newRO)
scene.update(repr.renderObjects, false)
if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false
requestDraw(true)
reprCount.next(reprRenderObjects.size)
commit(repr.renderObjects)
}
handleResize()
@@ -301,14 +283,9 @@ namespace Canvas3D {
}
const renderObjects = reprRenderObjects.get(repr)
if (renderObjects) {
isUpdating = true
renderObjects.forEach(o => scene.remove(o))
reprRenderObjects.delete(repr)
scene.update(void 0, false)
if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false
requestDraw(true)
reprCount.next(reprRenderObjects.size)
commit()
}
},
update: (repr, keepSphere) => {
@@ -323,6 +300,8 @@ namespace Canvas3D {
reprRenderObjects.clear()
scene.clear()
debugHelper.clear()
requestDraw(true)
reprCount.next(reprRenderObjects.size)
},
// draw,
@@ -334,8 +313,12 @@ namespace Canvas3D {
handleResize,
resetCamera: () => {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
requestDraw(true);
if (scene.isCommiting) {
cameraResetRequested = true
} else {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, p.cameraResetDurationMs)
requestDraw(true);
}
},
camera,
downloadScreenshot: () => {
@@ -351,13 +334,15 @@ namespace Canvas3D {
}
},
didDraw,
reprCount,
setProps: (props: Partial<Canvas3DProps>) => {
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
camera.setState({ mode: props.cameraMode })
}
if (props.cameraClipDistance !== undefined) p.cameraClipDistance = props.cameraClipDistance
if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]]
if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]]
if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) {
camera.setState({ fog: props.cameraFog })
}
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
if (props.postprocessing) postprocessing.setProps(props.postprocessing)
if (props.multiSample) multiSample.setProps(props.multiSample)
@@ -366,13 +351,15 @@ namespace Canvas3D {
if (props.debug) debugHelper.setProps(props.debug)
requestDraw(true)
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props)
},
get props() {
return {
cameraMode: camera.state.mode,
cameraClipDistance: p.cameraClipDistance,
clip: p.clip,
fog: p.fog,
cameraFog: camera.state.fog,
cameraResetDurationMs: p.cameraResetDurationMs,
postprocessing: { ...postprocessing.props },
multiSample: { ...multiSample.props },
@@ -396,7 +383,6 @@ namespace Canvas3D {
input.dispose()
controls.dispose()
renderer.dispose()
camera.dispose()
interactionHelper.dispose()
}
}

View File

@@ -9,26 +9,47 @@
*/
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { cameraLookAt, Viewport } from '../camera/util';
import InputObserver, { DragInput, WheelInput, ButtonsType, PinchInput } from '../../mol-util/input/input-observer';
import { Object3D } from '../../mol-gl/object3d';
import { Viewport } from '../camera/util';
import InputObserver, { DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
import { Binding } from '../../mol-util/binding';
const B = ButtonsType
const M = ModifiersKeys
const Trigger = Binding.Trigger
export const DefaultTrackballBindings = {
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate the 3D scene by dragging using ${triggers}'),
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate the 3D scene around the z-axis by dragging using ${triggers}'),
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan the 3D scene by dragging using ${triggers}'),
dragZoom: Binding.Empty,
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus the 3D scene by dragging using ${triggers}'),
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom the 3D scene by dragging using ${triggers}'),
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom the 3D scene by scrolling using ${triggers}'),
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Focus the 3D scene by scrolling using ${triggers}'),
scrollFocusZoom: Binding.Empty,
}
export const TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
rotateSpeed: PD.Numeric(5.0, { min: 0.1, max: 10, step: 0.1 }),
rotateSpeed: PD.Numeric(3.0, { min: 0.1, max: 10, step: 0.1 }),
zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
spin: PD.Boolean(false),
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
spinSpeed: PD.Numeric(1, { min: -100, max: 100, step: 1 }),
staticMoving: PD.Boolean(true, { isHidden: true }),
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true })
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
}
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
@@ -44,11 +65,10 @@ interface TrackballControls {
dispose: () => void
}
namespace TrackballControls {
export function create(input: InputObserver, object: Object3D & { target: Vec3 }, props: Partial<TrackballControlsProps> = {}): TrackballControls {
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props }
const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
const target: Vec3 = object.target
const viewport = Viewport()
let disposed = false
@@ -60,91 +80,116 @@ namespace TrackballControls {
let _isInteracting = false;
// For internal use
const lastPosition = Vec3.zero()
const lastPosition = Vec3()
const _eye = Vec3.zero()
const _eye = Vec3()
const _movePrev = Vec2.zero()
const _moveCurr = Vec2.zero()
const _rotPrev = Vec2()
const _rotCurr = Vec2()
const _rotLastAxis = Vec3()
let _rotLastAngle = 0
const _lastAxis = Vec3.zero()
let _lastAngle = 0
const _zRotPrev = Vec2()
const _zRotCurr = Vec2()
let _zRotLastAngle = 0
const _zoomStart = Vec2.zero()
const _zoomEnd = Vec2.zero()
const _zoomStart = Vec2()
const _zoomEnd = Vec2()
const _panStart = Vec2.zero()
const _panEnd = Vec2.zero()
const _focusStart = Vec2()
const _focusEnd = Vec2()
const _panStart = Vec2()
const _panEnd = Vec2()
// Initial values for reseting
const target0 = Vec3.clone(target)
const position0 = Vec3.clone(object.position)
const up0 = Vec3.clone(object.up)
const target0 = Vec3.clone(camera.target)
const position0 = Vec3.clone(camera.position)
const up0 = Vec3.clone(camera.up)
const mouseOnScreenVec2 = Vec2.zero()
const mouseOnScreenVec2 = Vec2()
function getMouseOnScreen(pageX: number, pageY: number) {
Vec2.set(
return Vec2.set(
mouseOnScreenVec2,
(pageX - viewport.x) / viewport.width,
(pageY - viewport.y) / viewport.height
);
return mouseOnScreenVec2;
}
const mouseOnCircleVec2 = Vec2.zero()
const mouseOnCircleVec2 = Vec2()
function getMouseOnCircle(pageX: number, pageY: number) {
Vec2.set(
return Vec2.set(
mouseOnCircleVec2,
((pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5)),
((viewport.height + 2 * (viewport.y - pageY)) / viewport.width) // screen.width intentional
(pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5),
(viewport.height + 2 * (viewport.y - pageY)) / viewport.width // screen.width intentional
);
return mouseOnCircleVec2;
}
const rotAxis = Vec3.zero()
const rotQuat = Quat.zero()
const rotEyeDir = Vec3.zero()
const rotObjUpDir = Vec3.zero()
const rotObjSideDir = Vec3.zero()
const rotMoveDir = Vec3.zero()
const rotAxis = Vec3()
const rotQuat = Quat()
const rotEyeDir = Vec3()
const rotObjUpDir = Vec3()
const rotObjSideDir = Vec3()
const rotMoveDir = Vec3()
function rotateCamera() {
Vec3.set(rotMoveDir, _moveCurr[0] - _movePrev[0], _moveCurr[1] - _movePrev[1], 0);
let angle = Vec3.magnitude(rotMoveDir);
const dx = _rotCurr[0] - _rotPrev[0]
const dy = _rotCurr[1] - _rotPrev[1]
Vec3.set(rotMoveDir, dx, dy, 0);
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed;
if (angle) {
Vec3.copy(_eye, object.position)
Vec3.sub(_eye, _eye, target)
Vec3.sub(_eye, camera.position, camera.target)
Vec3.normalize(rotEyeDir, Vec3.copy(rotEyeDir, _eye))
Vec3.normalize(rotObjUpDir, Vec3.copy(rotObjUpDir, object.up))
Vec3.normalize(rotEyeDir, _eye)
Vec3.normalize(rotObjUpDir, camera.up)
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir))
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, _moveCurr[1] - _movePrev[1])
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, _moveCurr[0] - _movePrev[0])
Vec3.add(rotMoveDir, Vec3.copy(rotMoveDir, rotObjUpDir), rotObjSideDir)
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy)
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx)
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir)
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
angle *= p.rotateSpeed;
Quat.setAxisAngle(rotQuat, rotAxis, angle)
Vec3.transformQuat(_eye, _eye, rotQuat)
Vec3.transformQuat(object.up, object.up, rotQuat)
Vec3.transformQuat(camera.up, camera.up, rotQuat)
Vec3.copy(_lastAxis, rotAxis)
_lastAngle = angle;
} else if (!p.staticMoving && _lastAngle) {
_lastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
Vec3.sub(_eye, Vec3.copy(_eye, object.position), target)
Quat.setAxisAngle(rotQuat, _lastAxis, _lastAngle)
Vec3.copy(_rotLastAxis, rotAxis)
_rotLastAngle = angle;
} else if (!p.staticMoving && _rotLastAngle) {
_rotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
Vec3.sub(_eye, camera.position, camera.target)
Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle)
Vec3.transformQuat(_eye, _eye, rotQuat)
Vec3.transformQuat(object.up, object.up, rotQuat)
Vec3.transformQuat(camera.up, camera.up, rotQuat)
}
Vec2.copy(_movePrev, _moveCurr)
Vec2.copy(_rotPrev, _rotCurr)
}
const zRotQuat = Quat()
function zRotateCamera() {
const dx = _zRotCurr[0] - _zRotPrev[0]
const dy = _zRotCurr[1] - _zRotPrev[1]
const angle = p.rotateSpeed * (-dx + dy) * -0.05
if (angle) {
Vec3.sub(_eye, camera.position, camera.target)
Quat.setAxisAngle(zRotQuat, _eye, angle)
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
_zRotLastAngle = angle;
} else if (!p.staticMoving && _zRotLastAngle) {
_zRotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
Vec3.sub(_eye, camera.position, camera.target)
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle)
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
}
Vec2.copy(_zRotPrev, _zRotCurr)
}
function zoomCamera() {
@@ -160,9 +205,23 @@ namespace TrackballControls {
}
}
const panMouseChange = Vec2.zero()
const panObjUp = Vec3.zero()
const panOffset = Vec3.zero()
function focusCamera() {
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
if (factor !== 0.0) {
const radius = Math.max(1, camera.state.radius + 10 * factor)
camera.setState({ radius })
}
if (p.staticMoving) {
Vec2.copy(_focusStart, _focusEnd)
} else {
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor
}
}
const panMouseChange = Vec2()
const panObjUp = Vec3()
const panOffset = Vec3()
function panCamera() {
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart)
@@ -170,14 +229,14 @@ namespace TrackballControls {
if (Vec2.squaredMagnitude(panMouseChange)) {
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed)
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), object.up)
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up)
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0])
Vec3.setMagnitude(panObjUp, object.up, panMouseChange[1])
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1])
Vec3.add(panOffset, panOffset, panObjUp)
Vec3.add(object.position, object.position, panOffset)
Vec3.add(target, target, panOffset)
Vec3.add(camera.position, camera.position, panOffset)
Vec3.add(camera.target, camera.target, panOffset)
if (p.staticMoving) {
Vec2.copy(_panStart, _panEnd)
@@ -193,14 +252,16 @@ namespace TrackballControls {
function checkDistances() {
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
Vec3.add(object.position, target, _eye)
Vec3.add(camera.position, camera.target, _eye)
Vec2.copy(_zoomStart, _zoomEnd)
Vec2.copy(_focusStart, _focusEnd)
}
if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) {
Vec3.setMagnitude(_eye, _eye, p.minDistance)
Vec3.add(object.position, target, _eye)
Vec3.add(camera.position, camera.target, _eye)
Vec2.copy(_zoomStart, _zoomEnd)
Vec2.copy(_focusStart, _focusEnd)
}
}
@@ -210,20 +271,19 @@ namespace TrackballControls {
if (lastUpdated === t) return;
if (p.spin) spin(t - lastUpdated);
Vec3.sub(_eye, object.position, target)
Vec3.sub(_eye, camera.position, camera.target)
rotateCamera()
zRotateCamera()
zoomCamera()
focusCamera()
panCamera()
Vec3.add(object.position, target, _eye)
Vec3.add(camera.position, camera.target, _eye)
checkDistances()
cameraLookAt(object.position, object.up, object.direction, target)
if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
Vec3.copy(lastPosition, object.position)
if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) {
Vec3.copy(lastPosition, camera.position)
}
lastUpdated = t;
@@ -231,54 +291,82 @@ namespace TrackballControls {
/** Reset object's vectors and the target vector to their initial values */
function reset() {
Vec3.copy(target, target0)
Vec3.copy(object.position, position0)
Vec3.copy(object.up, up0)
Vec3.copy(camera.target, target0)
Vec3.copy(camera.position, position0)
Vec3.copy(camera.up, up0)
Vec3.sub(_eye, object.position, target)
cameraLookAt(object.position, object.up, object.direction, target)
Vec3.copy(lastPosition, object.position)
Vec3.sub(_eye, camera.position, camera.target)
Vec3.copy(lastPosition, camera.position)
}
// listeners
function onDrag({ pageX, pageY, buttons, isStart }: DragInput) {
function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
_isInteracting = true;
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers)
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers)
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers)
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers)
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers)
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers)
getMouseOnCircle(pageX, pageY)
getMouseOnScreen(pageX, pageY)
if (isStart) {
if (buttons === ButtonsType.Flag.Primary) {
Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
Vec2.copy(_movePrev, _moveCurr)
} else if (buttons === ButtonsType.Flag.Auxilary) {
Vec2.copy(_zoomStart, getMouseOnScreen(pageX, pageY))
if (dragRotate) {
Vec2.copy(_rotCurr, mouseOnCircleVec2)
Vec2.copy(_rotPrev, _rotCurr)
}
if (dragRotateZ) {
Vec2.copy(_zRotCurr, mouseOnCircleVec2)
Vec2.copy(_zRotPrev, _zRotCurr)
}
if (dragZoom || dragFocusZoom) {
Vec2.copy(_zoomStart, mouseOnScreenVec2)
Vec2.copy(_zoomEnd, _zoomStart)
} else if (buttons === ButtonsType.Flag.Secondary) {
Vec2.copy(_panStart, getMouseOnScreen(pageX, pageY))
}
if (dragFocus) {
Vec2.copy(_focusStart, mouseOnScreenVec2)
Vec2.copy(_focusEnd, _focusStart)
}
if (dragPan) {
Vec2.copy(_panStart, mouseOnScreenVec2)
Vec2.copy(_panEnd, _panStart)
}
}
if (buttons === ButtonsType.Flag.Primary) {
Vec2.copy(_movePrev, _moveCurr)
Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
} else if (buttons === ButtonsType.Flag.Auxilary) {
Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY))
} else if (buttons === ButtonsType.Flag.Secondary) {
Vec2.copy(_panEnd, getMouseOnScreen(pageX, pageY))
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2)
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2)
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2)
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2)
if (dragFocusZoom) {
const dist = Vec3.distance(camera.state.position, camera.state.target);
camera.setState({ radius: dist / 5 })
}
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
}
function onInteractionEnd() {
_isInteracting = false;
}
function onWheel({ dy }: WheelInput) {
_zoomStart[1] -= dy * 0.0001
function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) {
const delta = absMax(dx, dy, dz)
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001
}
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
_focusEnd[1] += delta * 0.0001
}
}
function onPinch({ fraction }: PinchInput) {
_isInteracting = true;
_zoomStart[1] -= (fraction - 1) * 0.1
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_isInteracting = true;
_zoomEnd[1] += (fraction - 1) * 0.1
}
}
function dispose() {
@@ -295,7 +383,7 @@ namespace TrackballControls {
function spin(deltaT: number) {
const frameSpeed = (p.spinSpeed || 0) / 1000;
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
if (!_isInteracting) Vec2.add(_moveCurr, _movePrev, _spinSpeed);
if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
}
// force an update at start

View File

@@ -13,7 +13,7 @@ import Scene from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Sphere3D } from '../../mol-math/geometry';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { TransformData } from '../../mol-geo/geometry/transform-data';
import { sphereVertexCount } from '../../mol-geo/primitive/sphere';
import { ValueCell } from '../../mol-util';
@@ -81,7 +81,8 @@ export class BoundingSphereHelper {
}
})
this.scene.update(void 0, false);
this.scene.update(void 0, false)
this.scene.syncCommit()
}
syncVisibility() {

View File

@@ -11,13 +11,15 @@ import InputObserver, { ModifiersKeys, ButtonsType } from '../../mol-util/input/
import { RxEventHelper } from '../../mol-util/rx-event-helper';
type Canvas3D = import('../canvas3d').Canvas3D
type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
export class Canvas3dInteractionHelper {
private ev = RxEventHelper.create();
readonly events = {
highlight: this.ev<import('../canvas3d').Canvas3D.HighlightEvent>(),
click: this.ev<import('../canvas3d').Canvas3D.ClickEvent>(),
hover: this.ev<HoverEvent>(),
click: this.ev<ClickEvent>(),
};
private cX = -1;
@@ -52,14 +54,14 @@ export class Canvas3dInteractionHelper {
return;
}
// only highlight the latest
if (!this.inside || this.currentIdentifyT !== t) {
return;
}
const loci = this.getLoci(this.id);
// only broadcast the latest hover
if (!Representation.Loci.areEqual(this.prevLoci, loci)) {
this.events.highlight.next({ current: loci, prev: this.prevLoci, modifiers: this.modifiers });
this.events.hover.next({ current: loci, buttons: this.buttons, modifiers: this.modifiers });
this.prevLoci = loci;
}
}
@@ -75,14 +77,14 @@ export class Canvas3dInteractionHelper {
leave() {
this.inside = false;
if (this.prevLoci.loci !== EmptyLoci) {
const prev = this.prevLoci;
this.prevLoci = Representation.Loci.Empty;
this.events.highlight.next({ current: this.prevLoci, prev });
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, modifiers: this.modifiers });
}
}
move(x: number, y: number, modifiers: ModifiersKeys) {
move(x: number, y: number, buttons: ButtonsType, modifiers: ModifiersKeys) {
this.inside = true;
this.buttons = buttons;
this.modifiers = modifiers;
this.cX = x;
this.cY = y;
@@ -99,7 +101,7 @@ export class Canvas3dInteractionHelper {
modify(modifiers: ModifiersKeys) {
if (this.prevLoci.loci === EmptyLoci || ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
this.modifiers = modifiers;
this.events.highlight.next({ current: this.prevLoci, prev: this.prevLoci, modifiers: this.modifiers });
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, modifiers: this.modifiers });
}
dispose() {
@@ -108,8 +110,8 @@ export class Canvas3dInteractionHelper {
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 15) {
input.move.subscribe(({x, y, inside, buttons, modifiers }) => {
if (!inside || buttons) { return; }
this.move(x, y, modifiers);
if (!inside) return;
this.move(x, y, buttons, modifiers);
});
input.leave.subscribe(() => {

View File

@@ -10,6 +10,7 @@ import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { createTexture, Texture } from '../../mol-gl/webgl/texture';
import { Camera } from '../camera';
export class DrawPass {
colorTarget: RenderTarget
@@ -18,11 +19,11 @@ export class DrawPass {
private depthTarget: RenderTarget | null
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private debugHelper: BoundingSphereHelper) {
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
const { gl, extensions } = webgl
const width = gl.drawingBufferWidth
const height = gl.drawingBufferHeight
this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.colorTarget = createRenderTarget(webgl, width, height)
this.packedDepth = !extensions.depthTexture
this.depthTarget = this.packedDepth ? createRenderTarget(webgl, width, height) : null
this.depthTexture = this.depthTarget ? this.depthTarget.texture : createTexture(webgl, 'image-depth', 'depth', 'ushort', 'nearest')
@@ -42,29 +43,28 @@ export class DrawPass {
}
render(toDrawingBuffer: boolean) {
const { webgl, renderer, scene, debugHelper, colorTarget, depthTarget } = this
const { gl } = webgl
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
} else {
colorTarget.bind()
}
renderer.setViewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
renderer.render(scene, 'color', true)
renderer.setViewport(0, 0, colorTarget.width, colorTarget.height)
renderer.render(scene, camera, 'color', true)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, 'color', false)
renderer.render(debugHelper.scene, camera, 'color', false)
}
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (!toDrawingBuffer && depthTarget) {
depthTarget.bind()
renderer.render(scene, 'depth', true)
renderer.render(scene, camera, 'depth', true)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, 'depth', false)
renderer.render(debugHelper.scene, camera, 'depth', false)
}
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DrawPass } from './draw'
import { PostprocessingPass, PostprocessingParams } from './postprocessing'
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
import { Camera } from '../camera';
import { Viewport } from '../camera/util';
export const ImageParams = {
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
}
export type ImageProps = PD.Values<typeof ImageParams>
export class ImagePass {
private _width = 1024
private _height = 768
private _camera = new Camera()
private _colorTarget: RenderTarget
get colorTarget() { return this._colorTarget }
readonly drawPass: DrawPass
private readonly postprocessing: PostprocessingPass
private readonly multiSample: MultiSamplePass
get width() { return this._width }
get height() { return this._height }
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
const p = { ...PD.getDefaultValues(ImageParams), ...props }
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
this.setSize(this._width, this._height)
}
setSize(width: number, height: number) {
this._width = width
this._height = height
this.drawPass.setSize(width, height)
this.postprocessing.setSize(width, height)
this.multiSample.setSize(width, height)
}
setProps(props: Partial<ImageProps> = {}) {
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing)
if (props.multiSample) this.multiSample.setProps(props.multiSample)
}
render() {
Camera.copySnapshot(this._camera.state, this.camera.state)
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height)
this._camera.update()
this.renderer.setViewport(0, 0, this._width, this._height);
if (this.multiSample.enabled) {
this.multiSample.render(false)
this._colorTarget = this.multiSample.colorTarget
} else {
this.drawPass.render(false)
if (this.postprocessing.enabled) {
this.postprocessing.render(false)
this._colorTarget = this.postprocessing.target
} else {
this._colorTarget = this.drawPass.colorTarget
}
}
}
getImageData(width: number, height: number) {
this.setSize(width, height)
this.render()
const pd = this.colorTarget.getPixelData()
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height)
}
}

View File

@@ -54,6 +54,7 @@ export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
export class MultiSamplePass {
props: MultiSampleProps
colorTarget: RenderTarget
private composeTarget: RenderTarget
private holdTarget: RenderTarget
@@ -65,6 +66,7 @@ export class MultiSamplePass {
constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
const { gl } = webgl
this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.composeTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.holdTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture)
@@ -92,6 +94,7 @@ export class MultiSamplePass {
}
setSize(width: number, height: number) {
this.colorTarget.setSize(width, height)
this.composeTarget.setSize(width, height)
this.holdTarget.setSize(width, height)
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height))
@@ -102,15 +105,15 @@ export class MultiSamplePass {
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel
}
render() {
render(toDrawingBuffer: boolean) {
if (this.props.mode === 'temporal') {
this.renderTemporalMultiSample()
this.renderTemporalMultiSample(toDrawingBuffer)
} else {
this.renderMultiSample()
this.renderMultiSample(toDrawingBuffer)
}
}
private renderMultiSample() {
private renderMultiSample(toDrawingBuffer: boolean) {
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this
const { gl, state } = webgl
@@ -135,7 +138,7 @@ export class MultiSamplePass {
for (let i = 0; i < offsetList.length; ++i) {
const offset = offsetList[i]
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
camera.updateMatrices()
camera.update()
// the theory is that equal weights for each sample lead to an accumulation of rounding
// errors. The following equation varies the sampleWeight per sample so that it is uniformly
@@ -168,16 +171,20 @@ export class MultiSamplePass {
ValueCell.update(compose.values.tColor, composeTarget.texture)
compose.update()
webgl.unbindFramebuffer()
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
this.colorTarget.bind()
}
gl.viewport(0, 0, width, height)
state.disable(gl.BLEND)
compose.render()
camera.viewOffset.enabled = false
camera.updateMatrices()
camera.update()
}
private renderTemporalMultiSample() {
private renderTemporalMultiSample(toDrawingBuffer: boolean) {
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this
const { gl, state } = webgl
@@ -223,7 +230,7 @@ export class MultiSamplePass {
for (let i = 0; i < numSamplesPerFrame; ++i) {
const offset = offsetList[this.sampleIndex]
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
camera.updateMatrices()
camera.update()
// render scene and optionally postprocess
drawPass.render(false)
@@ -252,7 +259,11 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, 1.0)
ValueCell.update(compose.values.tColor, composeTarget.texture)
compose.update()
webgl.unbindFramebuffer()
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
this.colorTarget.bind()
}
gl.viewport(0, 0, width, height)
state.disable(gl.BLEND)
compose.render()
@@ -261,7 +272,11 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight)
ValueCell.update(compose.values.tColor, holdTarget.texture)
compose.update()
webgl.unbindFramebuffer()
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
this.colorTarget.bind()
}
gl.viewport(0, 0, width, height)
if (accumulationWeight === 0) state.disable(gl.BLEND)
else state.enable(gl.BLEND)
@@ -269,7 +284,7 @@ export class MultiSamplePass {
}
camera.viewOffset.enabled = false
camera.updateMatrices()
camera.update()
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1
}
}

View File

@@ -10,20 +10,24 @@ import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { PickingId } from '../../mol-geo/geometry/picking';
import { decodeFloatRGB } from '../../mol-util/float-packing';
const readBuffer = new Uint8Array(4)
import { Camera } from '../camera';
export class PickPass {
pickDirty = true
objectPickTarget: RenderTarget
instancePickTarget: RenderTarget
groupPickTarget: RenderTarget
private objectBuffer: Uint8Array
private instanceBuffer: Uint8Array
private groupBuffer: Uint8Array
private pickScale: number
private pickWidth: number
private pickHeight: number
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private pickBaseScale: number) {
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) {
const { gl } = webgl
const width = gl.drawingBufferWidth
const height = gl.drawingBufferHeight
@@ -31,35 +35,73 @@ export class PickPass {
this.pickScale = pickBaseScale / webgl.pixelRatio
this.pickWidth = Math.round(width * this.pickScale)
this.pickHeight = Math.round(height * this.pickScale)
this.objectPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
this.instancePickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
this.groupPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
this.setupBuffers()
}
private setupBuffers() {
const bufferSize = this.pickWidth * this.pickHeight * 4
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
this.objectBuffer = new Uint8Array(bufferSize)
this.instanceBuffer = new Uint8Array(bufferSize)
this.groupBuffer = new Uint8Array(bufferSize)
}
}
setSize(width: number, height: number) {
this.pickScale = this.pickBaseScale / this.webgl.pixelRatio
this.pickWidth = Math.round(width * this.pickScale)
this.pickHeight = Math.round(height * this.pickScale)
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight)
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight)
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight)
this.setupBuffers()
}
render() {
const { renderer, scene } = this
const { renderer, scene, camera } = this
renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
this.objectPickTarget.bind();
renderer.render(scene, 'pickObject', true);
renderer.render(scene, camera, 'pickObject', true);
this.instancePickTarget.bind();
renderer.render(scene, 'pickInstance', true);
renderer.render(scene, camera, 'pickInstance', true);
this.groupPickTarget.bind();
renderer.render(scene, 'pickGroup', true);
renderer.render(scene, camera, 'pickGroup', true);
this.pickDirty = false
}
private syncBuffers() {
const { webgl } = this
this.objectPickTarget.bind()
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.objectBuffer)
this.instancePickTarget.bind()
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.instanceBuffer)
this.groupPickTarget.bind()
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.groupBuffer)
}
private getId(x: number, y: number, buffer: Uint8Array) {
const idx = (y * this.pickWidth + x) * 4
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2])
}
identify(x: number, y: number): PickingId | undefined {
const { webgl, pickScale } = this
const { gl } = webgl
if (this.pickDirty) this.render()
if (this.pickDirty) {
this.render()
this.syncBuffers()
}
x *= webgl.pixelRatio
y *= webgl.pixelRatio
@@ -68,19 +110,13 @@ export class PickPass {
const xp = Math.round(x * pickScale)
const yp = Math.round(y * pickScale)
this.objectPickTarget.bind()
webgl.readPixels(xp, yp, 1, 1, readBuffer)
const objectId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
const objectId = this.getId(xp, yp, this.objectBuffer)
if (objectId === -1) return
this.instancePickTarget.bind()
webgl.readPixels(xp, yp, 1, 1, readBuffer)
const instanceId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
const instanceId = this.getId(xp, yp, this.instanceBuffer)
if (instanceId === -1) return
this.groupPickTarget.bind()
webgl.readPixels(xp, yp, 1, 1, readBuffer)
const groupId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
const groupId = this.getId(xp, yp, this.groupBuffer)
if (groupId === -1) return
return { objectId, instanceId, groupId }

View File

@@ -51,7 +51,7 @@ export const PostprocessingParams = {
occlusionEnable: PD.Boolean(false),
occlusionKernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
occlusionRadius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
occlusionRadius: PD.Numeric(32, { min: 0, max: 256, step: 1 }),
outlineEnable: PD.Boolean(false),
outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
@@ -160,10 +160,10 @@ export class PostprocessingPass {
}
render(toDrawingBuffer: boolean) {
ValueCell.update(this.renderable.values.uFar, this.camera.state.far)
ValueCell.update(this.renderable.values.uNear, this.camera.state.near)
ValueCell.update(this.renderable.values.uFogFar, this.camera.state.fogFar)
ValueCell.update(this.renderable.values.uFogNear, this.camera.state.fogNear)
ValueCell.update(this.renderable.values.uFar, this.camera.far)
ValueCell.update(this.renderable.values.uNear, this.camera.near)
ValueCell.update(this.renderable.values.uFogFar, this.camera.fogFar)
ValueCell.update(this.renderable.values.uFogNear, this.camera.fogNear)
ValueCell.update(this.renderable.values.dOrthographic, this.camera.state.mode === 'orthographic' ? 1 : 0)
const { gl, state } = this.webgl

View File

@@ -4,16 +4,57 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/** resize canvas to container element */
/** Set canvas size taking `devicePixelRatio` into account */
export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height: number) {
canvas.width = Math.round(window.devicePixelRatio * width)
canvas.height = Math.round(window.devicePixelRatio * height)
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` })
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
let w = window.innerWidth
let h = window.innerHeight
let width = window.innerWidth
let height = window.innerHeight
if (container !== document.body) {
let bounds = container.getBoundingClientRect()
w = bounds.right - bounds.left
h = bounds.bottom - bounds.top
width = bounds.right - bounds.left
height = bounds.bottom - bounds.top
}
canvas.width = window.devicePixelRatio * w
canvas.height = window.devicePixelRatio * h
Object.assign(canvas.style, { width: `${w}px`, height: `${h}px` })
setCanvasSize(canvas, width, height)
}
function _canvasToBlob(canvas: HTMLCanvasElement, callback: BlobCallback, type?: string, quality?: any) {
const bin = atob(canvas.toDataURL(type, quality).split(',')[1])
const len = bin.length
const len32 = len >> 2
const a8 = new Uint8Array(len)
const a32 = new Uint32Array(a8.buffer, 0, len32)
let j = 0
for (let i = 0; i < len32; ++i) {
a32[i] = bin.charCodeAt(j++) |
bin.charCodeAt(j++) << 8 |
bin.charCodeAt(j++) << 16 |
bin.charCodeAt(j++) << 24
}
let tailLength = len & 3;
while (tailLength--) a8[j] = bin.charCodeAt(j++)
callback(new Blob([a8], { type: type || 'image/png' }));
}
export async function canvasToBlob(canvas: HTMLCanvasElement, type?: string, quality?: any): Promise<Blob> {
return new Promise((resolve, reject) => {
const callback = (blob: Blob | null) => {
if (blob) resolve(blob)
else reject('no blob returned')
}
if (!HTMLCanvasElement.prototype.toBlob) {
_canvasToBlob(canvas, callback, type, quality)
} else {
canvas.toBlob(callback, type, quality)
}
})
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 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>
@@ -7,6 +7,8 @@
import * as ColumnHelpers from './column-helpers'
import { Tensor as Tensors } from '../../mol-math/linear-algebra'
import { Tokens } from '../../mol-io/reader/common/text/tokenizer';
import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../mol-io/reader/common/text/number-parser';
interface Column<T> {
readonly schema: Column.Schema,
@@ -71,6 +73,7 @@ namespace Column {
rowCount: number,
schema: T,
valueKind?: (row: number) => ValueKind,
areValuesEqual?: (rowA: number, rowB: number) => boolean
}
export interface ArraySpec<T extends Schema> {
@@ -128,6 +131,37 @@ namespace Column {
return arrayColumn({ array, schema: Schema.str });
}
export function ofIntTokens(tokens: Tokens) {
const { count, data, indices } = tokens
return lambdaColumn({
value: (row: number) => fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0,
rowCount: count,
schema: Schema.int,
});
}
export function ofFloatTokens(tokens: Tokens) {
const { count, data, indices } = tokens
return lambdaColumn({
value: (row: number) => fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0,
rowCount: count,
schema: Schema.float,
});
}
export function ofStringTokens(tokens: Tokens) {
const { count, data, indices } = tokens
return lambdaColumn({
value: (row: number) => {
const ret = data.substring(indices[2 * row], indices[2 * row + 1]);
if (ret === '.' || ret === '?') return '';
return ret;
},
rowCount: count,
schema: Schema.str,
});
}
export function window<T>(column: Column<T>, start: number, end: number) {
return windowColumn(column, start, end);
}
@@ -214,7 +248,7 @@ function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schem
}
}
function lambdaColumn<T extends Column.Schema>({ value, valueKind, rowCount, schema }: Column.LambdaSpec<T>): Column<T['T']> {
function lambdaColumn<T extends Column.Schema>({ value, valueKind, areValuesEqual, rowCount, schema }: Column.LambdaSpec<T>): Column<T['T']> {
return {
schema: schema,
__array: void 0,
@@ -227,7 +261,7 @@ function lambdaColumn<T extends Column.Schema>({ value, valueKind, rowCount, sch
for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start);
return array;
},
areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB)
areValuesEqual: areValuesEqual ? areValuesEqual : (rowA, rowB) => value(rowA) === value(rowB)
}
}
@@ -268,7 +302,7 @@ function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Colu
}
}
function windowColumn<T>(column: Column<T>, start: number, end: number) {
function windowColumn<T>(column: Column<T>, start: number, end: number): Column<T> {
if (!column.isDefined) return Column.Undefined(end - start, column.schema);
if (start === 0 && end === column.rowCount) return column;
if (!!column.__array && ColumnHelpers.isTypedArray(column.__array)) return windowTyped(column, start, end);

View File

@@ -218,6 +218,16 @@ namespace Table {
return ret;
}
export function toArrays<S extends Schema>(table: Table<S>) {
const arrays: { [k: string]: ArrayLike<any> } = {}
const { _columns } = table;
for (let i = 0; i < _columns.length; i++) {
const c = _columns[i]
arrays[c] = table[c].toArray();
}
return arrays as { [k in keyof S]: ArrayLike<S[k]['T']> }
}
export function formatToString<S extends Schema>(table: Table<S>) {
const sb = StringBuilder.create();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -65,6 +65,73 @@ describe('sortedArray', () => {
test('intersectionSize', SortedArray.intersectionSize(a1234, a2468), 2);
// console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
// console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
it('union1', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 836, 837, 838, 839, 840, 841, 842, 843]),
SortedArray.ofSortedArray([835])
),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843])
)
})
it('union2', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([830, 832, 833]),
SortedArray.ofSortedArray([831])
),
SortedArray.ofSortedArray([830, 831, 832, 833])
)
})
it('union3ab', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835]),
SortedArray.ofSortedArray([836, 837, 838, 839, 840, 841, 842, 843])
),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843])
)
})
it('union3ba', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([836, 837, 838, 839, 840, 841, 842, 843]),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835])
),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843])
)
})
it('union4', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([1, 3, 5, 7, 9]),
SortedArray.ofSortedArray([2, 4, 6, 8])
),
SortedArray.ofSortedArray([1, 2, 3, 4, 5, 6, 7, 8, 9])
)
})
it('union5', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([2, 3, 4, 20, 21, 22]),
SortedArray.ofSortedArray([10, 11, 12])
),
SortedArray.ofSortedArray([2, 3, 4, 10, 11, 12, 20, 21, 22])
)
})
it('union6', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([768, 769, 770, 771, 772, 773, 774, 775, 776, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819]),
SortedArray.ofSortedArray([1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758])
),
SortedArray.ofSortedArray([768, 769, 770, 771, 772, 773, 774, 775, 776, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819])
)
})
});

View File

@@ -17,7 +17,7 @@ describe('rangesArray', () => {
it(`iterator, ${name}`, () => {
const rangesIt = SortedRanges.transientSegments(ranges, set)
const { index, start, end } = expectedValues
let i = 0
while (rangesIt.hasNext) {
const segment = rangesIt.move()
@@ -41,7 +41,7 @@ describe('rangesArray', () => {
testIterator('two ranges',
SortedRanges.ofSortedRanges([1, 2, 3, 4]),
OrderedSet.ofBounds(1, 4),
OrderedSet.ofBounds(1, 5),
{ index: [0, 1], start: [0, 2], end: [2, 4] }
)
testIterator('first range',
@@ -62,7 +62,7 @@ describe('rangesArray', () => {
testIterator('set in second range and beyond',
SortedRanges.ofSortedRanges([1, 2, 3, 4]),
SortedArray.ofSortedArray([3, 10]),
{ index: [1], start: [0], end: [2] }
{ index: [1], start: [0], end: [1] }
)
testIterator('length 1 range',
SortedRanges.ofSortedRanges([1, 1, 3, 4]),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -9,6 +9,7 @@ import Tuple from '../tuple'
export const Empty = Tuple.Zero;
export function ofRange(min: number, max: number) { return max < min ? Tuple.create(min, min) : Tuple.create(min, max + 1); }
export function ofBounds(start: number, end: number) { return end <= start ? Tuple.create(start, start) : Tuple.create(start, end); }
export function ofLength(length: number) { return length < 0 ? Tuple.create(0, 0) : Tuple.create(0, length); }
export const is = Tuple.is;
export const start = Tuple.fst;
@@ -17,6 +18,7 @@ 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 hashCode = Tuple.hashCode;
export const toString = Tuple.toString;
export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); }
/** Returns the index of `x` in `set` or -1 if not found. */

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -36,6 +36,8 @@ export function end(set: OrderedSetImpl) { return I.is(set) ? I.end(set) : S.end
export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); }
// TODO: possibly add more hash functions to allow for multilevel hashing.
export function toString(set: OrderedSetImpl) { return I.is(set) ? I.toString(set) : S.toString(set); }
export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) {
if (I.is(a)) {
if (I.is(b)) return I.areEqual(a, b);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -35,6 +35,11 @@ export function hashCode(xs: Nums) {
if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s >> 1]);
return hash3(s, xs[0], xs[s - 1]);
}
export function toString(xs: Nums) {
const s = xs.length;
if (s > 5) return `[${xs[0]}, ${xs[1]}, ..., ${xs[s - 1]}], length ${s}`;
return `[${(xs as number[]).join(', ')}]`;
}
/** Returns the index of `x` in `set` or -1 if not found. */
export function indexOf(xs: Nums, v: number) {
@@ -42,8 +47,10 @@ export function indexOf(xs: Nums, v: number) {
return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
}
export function indexOfInInterval(xs: Nums, v: number, bounds: Interval) {
return indexOfInRange(xs, v, Interval.start(bounds), Interval.end(bounds))
}
export function indexOfInRange(xs: Nums, v: number, s: number, e: number) {
const l = xs.length;
const s = Interval.start(bounds), e = Interval.end(bounds);
return l === 0 || e <= s ? -1 : xs[s] <= v && v <= xs[e - 1] ? binarySearchRange(xs, v, s, e) : -1;
}
export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; }
@@ -60,6 +67,11 @@ export function areEqual(a: Nums, b: Nums) {
return true;
}
/**
* Returns 0 if `v` is smaller or equal the first element of `xs`
* Returns length of `xs` if `v` is bigger than the last element of `xs`
* Otherwise returns the first index where the value of `xs` is equal or bigger than `v`
*/
export function findPredecessorIndex(xs: Nums, v: number) {
const len = xs.length;
if (v <= xs[0]) return 0;
@@ -159,28 +171,30 @@ export function isSubset(a: Nums, b: Nums) {
return equal === lenB;
}
export function union(a: Nums, b: Nums) {
export function union(a: Nums, b: Nums): Nums {
if (a === b) return a;
const lenA = a.length, lenB = b.length;
if (lenA === 0) return b;
if (lenB === 0) return a;
if (a[0] > b[0]) return union(b, a);
const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
const commonCount = getCommonCount(a, b, startI, startJ, endI, endJ);
const lenA = a.length, lenB = b.length;
// A === B || B is subset of A ==> A
if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a;
// A is subset of B ===> B
if (commonCount === lenA) return b;
const indices = new Int32Array(lenA + lenB - commonCount);
let offset = 0;
let i = 0, j = 0, offset = 0;
// insert the "prefixes"
for (let k = 0; k < startI; k++) indices[offset++] = a[k];
for (let k = 0; k < startJ; k++) indices[offset++] = b[k];
for (i = 0; i < startI; i++) indices[offset++] = a[i];
while (j < endJ && a[startI] > b[j]) indices[offset++] = b[j++];
// insert the common part
let i = startI;
let j = startJ;
while (i < endI && j < endJ) {
const x = a[i], y = b[j];
if (x < y) { indices[offset++] = x; i++; }

View File

@@ -14,6 +14,8 @@ namespace Interval {
export const ofRange: <T extends number = number>(min: T, max: T) => Interval<T> = Impl.ofRange as any;
/** Create interval from bounds [start, end), i.e. [start, end - 1] */
export const ofBounds: <T extends number = number>(start: T, end: T) => Interval<T> = Impl.ofBounds as any;
/** Create interval from length [0, length), i.e. [0, length - 1] */
export const ofLength: <T extends number = number>(length: T) => Interval<T> = Impl.ofLength as any;
export const is: <T extends number = number>(v: any) => v is Interval<T> = Impl.is as any;
/** Test if a value is within the bounds of the interval */
@@ -34,6 +36,8 @@ namespace Interval {
export const size: <T extends number = number>(interval: Interval<T>) => number = Impl.size as any;
/** Hash code describing the interval */
export const hashCode: <T extends number = number>(interval: Interval<T>) => number = Impl.hashCode as any;
/** String representation of the interval */
export const toString: <T extends number = number>(interval: Interval<T>) => string = Impl.toString as any;
/** Test if two intervals are identical */
export const areEqual: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.areEqual as any;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -8,7 +8,6 @@ import * as Base from './impl/ordered-set'
import Interval from './interval'
import SortedArray from './sorted-array';
/** test */
namespace OrderedSet {
export const Empty: OrderedSet = Base.Empty as any;
export const ofSingleton: <T extends number = number>(value: T) => OrderedSet<T> = Base.ofSingleton as any;
@@ -22,12 +21,14 @@ namespace OrderedSet {
export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any;
/** Returns the index of `x` in `set` or -1 if not found. */
export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any;
/** Returns the value in `set` at index `i`. */
export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any;
export const min: <T extends number = number>(set: OrderedSet<T>) => T = Base.min as any;
export const max: <T extends number = number>(set: OrderedSet<T>) => T = Base.max as any;
export const start: <T extends number = number>(set: OrderedSet<T>) => T = Base.start as any;
export const end: <T extends number = number>(set: OrderedSet<T>) => T = Base.end as any;
/** Number of elements in the OrderedSet */
export const size: <T extends number = number>(set: OrderedSet<T>) => number = Base.size as any;
export const hashCode: <T extends number = number>(set: OrderedSet<T>) => number = Base.hashCode as any;
@@ -38,8 +39,14 @@ namespace OrderedSet {
export const union: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.union as any;
export const intersect: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.intersect as any;
/** Returns elements of `a` that are not in `b`, i.e `a` - `b` */
export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any;
/**
* Returns 0 if `x` is smaller or equal the first element of `set`
* Returns length of `set` if `x` is bigger than the last element of `set`
* Otherwise returns the first index where the value of `set` is equal or bigger than `x`
*/
export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any;
export const findPredecessorIndexInInterval: <T extends number = number>(set: OrderedSet<T>, x: T, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
export const findRange: <T extends number = number>(set: OrderedSet<T>, min: T, max: T) => Interval = Base.findRange as any;
@@ -62,10 +69,12 @@ namespace OrderedSet {
OrderedSet.forEach(set, v => array.push(v))
return array
}
export function toString<T extends number = number>(set: OrderedSet<T>): string {
return Base.toString(set)
}
}
/** Represents bla */
type OrderedSet<T extends number = number> = SortedArray<T> | Interval<T>
export default OrderedSet

View File

@@ -18,7 +18,7 @@ namespace Segmentation {
export const getSegment: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, value: T) => number = Impl.getSegment as any;
export const projectValue: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
// Segment iterator that mutates a single segment object to mark all the segments.
/** Segment iterator that mutates a single segment object to mark all the segments. */
export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment) => Impl.SegmentIterator<I> = Impl.segments as any;
export type SegmentIterator<I extends number = number> = Impl.SegmentIterator<I>

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -12,9 +12,9 @@ namespace SortedArray {
export const ofUnsortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofUnsortedArray as any;
export const ofSingleton: <T extends number = number>(v: number) => SortedArray<T> = Impl.ofSingleton as any;
export const ofSortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofSortedArray as any;
// create sorted array [min, max] (it DOES contain the max value)
/** create sorted array [min, max] (it DOES contain the max value) */
export const ofRange: <T extends number = number>(min: T, max: T) => SortedArray<T> = Impl.ofRange as any;
// create sorted array [min, max) (it does NOT contain the max value)
/** 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;
@@ -22,15 +22,17 @@ namespace SortedArray {
/** Returns the index of `x` in `set` or -1 if not found. */
export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any;
export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
export const indexOfInRange: <T extends number = number>(array: SortedArray<T>, x: number, start: number, end: number) => number = Impl.indexOfInRange as any;
// array[0]
/** Returns `array[0]` */
export const start: <T extends number = number>(array: SortedArray<T>) => T = Impl.start as any;
// array[array.length - 1] + 1
/** Returns `array[array.length - 1] + 1` */
export const end: <T extends number = number>(array: SortedArray<T>) => T = Impl.end as any;
export const min: <T extends number = number>(array: SortedArray<T>) => T = Impl.min as any;
export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;
export const size: <T extends number = number>(array: SortedArray<T>) => number = Impl.size as any;
export const hashCode: <T extends number = number>(array: SortedArray<T>) => number = Impl.hashCode as any;
export const toString: <T extends number = number>(array: SortedArray<T>) => string = Impl.toString as any;
export const areEqual: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areEqual as any;
export const areIntersecting: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areIntersecting as any;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -23,6 +23,64 @@ namespace SortedRanges {
}
return size
}
export function count<T extends number = number>(ranges: SortedRanges<T>) { return ranges.length / 2 }
export function startAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2]
}
export function endAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2 + 1] + 1
}
export function minAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2]
}
export function maxAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2 + 1]
}
export function areEqual<T extends number = number>(a: SortedRanges<T>, b: SortedRanges<T>) {
if (a.length !== b.length) return false
for (let i = 0, il = a.length; i < il; ++i) {
if (a[i] !== b[i]) return false
}
return true
}
export function forEach<T extends number = number>(ranges: SortedRanges<T>, f: (value: T, i: number) => void) {
let k = 0
for (let i = 0, il = ranges.length; i < il; i += 2) {
for (let j = ranges[i], jl = ranges[i + 1]; j <= jl; ++j) {
f(j, k);
++k
}
}
}
/** Returns if a value of `set` is included in `ranges` */
export function has<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
return firstIntersectionIndex(ranges, set) !== -1
}
/** Returns if a value of `set` is included in `ranges` from given index */
export function hasFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number) {
return firstIntersectionIndexFrom(ranges, set, from) !== -1
}
export function firstIntersectionIndex<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>): number {
return firstIntersectionIndexFrom(ranges, set, 0)
}
export function firstIntersectionIndexFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number): number {
if (minAt(ranges, from) > OrderedSet.max(set) || max(ranges) < OrderedSet.min(set)) return -1
for (let i = from, il = count(ranges); i < il; ++i) {
const interval = Interval.ofRange(minAt(ranges, i), maxAt(ranges, i))
if (OrderedSet.areIntersecting(interval, set)) return i
}
return -1
}
export function transientSegments<T extends number = number, I extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
return new Iterator<T, I>(ranges, set)
@@ -32,53 +90,27 @@ namespace SortedRanges {
private value: Segmentation.Segment<I> = { index: 0 as I, start: 0 as T, end: 0 as T }
private curIndex = 0
private maxIndex = 0
private interval: Interval<T>
private curMin: T = 0 as T
hasNext: boolean = false;
updateInterval() {
this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1])
}
updateValue() {
this.value.index = this.curIndex / 2 as I
this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex])
this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1
private updateValue() {
this.value.index = this.curIndex as I
this.value.start = OrderedSet.findPredecessorIndex(this.set, startAt(this.ranges, this.curIndex))
this.value.end = OrderedSet.findPredecessorIndex(this.set, endAt(this.ranges, this.curIndex))
}
move() {
if (this.hasNext) {
this.updateValue()
while (this.curIndex <= this.maxIndex) {
this.curIndex += 2
this.curMin = Interval.end(this.interval)
this.updateInterval()
if (Interval.min(this.interval) >= this.curMin && OrderedSet.areIntersecting(this.interval, this.set)) break
}
this.hasNext = this.curIndex <= this.maxIndex
this.curIndex = firstIntersectionIndexFrom(this.ranges, this.set, this.curIndex + 1)
this.hasNext = this.curIndex !== -1
}
return this.value;
}
getRangeIndex(value: number) {
const index = SortedArray.findPredecessorIndex(this.ranges, value)
return (index % 2 === 1) ? index - 1 : index
}
constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) {
// TODO cleanup, refactor to make it clearer
const min = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.min(set))
const max = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.max(set) + 1)
if (ranges.length && min !== max) {
this.curIndex = this.getRangeIndex(OrderedSet.min(set))
this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set)))
this.curMin = this.ranges[this.curIndex]
this.updateInterval()
}
this.hasNext = ranges.length > 0 && min !== max && this.curIndex <= this.maxIndex
this.curIndex = firstIntersectionIndex(ranges, set)
this.hasNext = this.curIndex !== -1
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -73,6 +73,11 @@ namespace IntTuple {
_float64[0] = t as any;
return hash2(_int32[0], _int32[1]);
}
export function toString(t: IntTuple) {
_float64[0] = t as any;
return `(${_int32[0]}, ${_int32[1]})`;
}
}
export default IntTuple

View File

@@ -73,7 +73,7 @@ export function sortedCantorPairing(a: number, b: number) {
/**
* 32 bit FNV-1a hash, see http://isthe.com/chongo/tech/comp/fnv/
*/
export function hashFnv32a(array: number[]) {
export function hashFnv32a(array: ArrayLike<number>) {
let hval = 0x811c9dc5;
for (let i = 0, il = array.length; i < il; ++i) {
hval ^= array[i];

View File

@@ -13,7 +13,7 @@ import { Color } from '../../mol-util/color';
import { Vec3 } from '../../mol-math/linear-algebra';
import { TransformData, createIdentityTransform } from './transform-data';
import { Theme } from '../../mol-theme/theme';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { NullLocation } from '../../mol-model/location';
import { UniformColorTheme } from '../../mol-theme/color/uniform';
import { UniformSizeTheme } from '../../mol-theme/size/uniform';

View File

@@ -21,7 +21,7 @@ import { transformPositionArray } from '../../../mol-geo/util';
import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
import { Theme } from '../../../mol-theme/theme';
import { RenderableState } from '../../../mol-gl/renderable';
import { ColorListOptions, ColorListName } from '../../../mol-util/color/scale';
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
import { Color } from '../../../mol-util/color';
import { BaseGeometry } from '../base';
import { createEmptyOverpaint } from '../overpaint-data';
@@ -81,7 +81,7 @@ export namespace DirectVolume {
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
]),
list: PD.ColorScale<ColorListName>('RedYellowBlue', ColorListOptions),
list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
}
export type Params = typeof Params

View File

@@ -9,7 +9,7 @@ import { spline } from '../../../mol-math/interpolate';
import { ColorScale, Color } from '../../../mol-util/color';
import { ValueCell } from '../../../mol-util';
import { Vec2 } from '../../../mol-math/linear-algebra';
import { ColorListName } from '../../../mol-util/color/scale';
import { ColorListName } from '../../../mol-util/color/lists';
export interface ControlPoint { x: number, alpha: number }

View File

@@ -177,8 +177,8 @@ export namespace Lines {
}
function getBoundingSphere(lineStart: Float32Array, lineEnd: Float32Array, lineCount: number, transform: Float32Array, transformCount: number) {
const start = calculateBoundingSphere(lineStart, lineCount * 4, transform, transformCount)
const end = calculateBoundingSphere(lineEnd, lineCount * 4, transform, transformCount)
const start = calculateBoundingSphere(lineStart, lineCount * 4, transform, transformCount, 0, 4)
const end = calculateBoundingSphere(lineEnd, lineCount * 4, transform, transformCount, 0, 4)
return {
boundingSphere: Sphere3D.expandBySphere(start.boundingSphere, end.boundingSphere),
invariantBoundingSphere: Sphere3D.expandBySphere(start.invariantBoundingSphere, end.invariantBoundingSphere)

View File

@@ -13,57 +13,6 @@ export type MarkerData = {
uMarkerTexDim: ValueCell<Vec2>
}
export enum MarkerAction {
Highlight,
RemoveHighlight,
Select,
Deselect,
Toggle,
Clear
}
export function applyMarkerAction(array: Uint8Array, start: number, end: number, action: MarkerAction) {
let changed = false
for (let i = start; i < end; ++i) {
let v = array[i]
switch (action) {
case MarkerAction.Highlight:
if (v % 2 === 0) {
v += 1
}
break
case MarkerAction.RemoveHighlight:
if (v % 2 !== 0) {
v -= 1
}
break
case MarkerAction.Select:
if (v < 2) v += 2
// v += 2
break
case MarkerAction.Deselect:
// if (v >= 2) {
// v -= 2
// }
v = v % 2
break
case MarkerAction.Toggle:
if (v >= 2) {
v -= 2
} else {
v += 2
}
break
case MarkerAction.Clear:
v = 0
break
}
changed = array[i] !== v || changed
array[i] = v
}
return changed
}
export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array)
if (markerData) {

View File

@@ -1,13 +1,15 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4 } from '../../../../mol-math/linear-algebra';
import { MeshBuilder } from '../mesh-builder';
import { Primitive } from '../../../primitive/primitive';
import { Primitive, transformPrimitive } from '../../../primitive/primitive';
import { Cylinder, CylinderProps } from '../../../primitive/cylinder';
import { Prism } from '../../../primitive/prism';
import { polygon } from '../../../primitive/polygon';
const cylinderMap = new Map<string, Primitive>()
const up = Vec3.create(0, 1, 0)
@@ -34,7 +36,12 @@ function getCylinder(props: CylinderProps) {
const key = JSON.stringify(props)
let cylinder = cylinderMap.get(key)
if (cylinder === undefined) {
cylinder = Cylinder(props)
if (props.radialSegments && props.radialSegments <= 4) {
const box = Prism(polygon(4, true, props.radiusTop), props)
cylinder = transformPrimitive(box, Mat4.rotX90)
} else {
cylinder = Cylinder(props)
}
cylinderMap.set(key, cylinder)
}
return cylinder

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4 } from '../../../../mol-math/linear-algebra';
import { MeshBuilder } from '../mesh-builder';
import { getSphere } from './sphere';
const tmpEllipsoidMat = Mat4.identity()
const tmpVec = Vec3()
function setEllipsoidMat(m: Mat4, center: Vec3, dirMajor: Vec3, dirMinor: Vec3, radiusScale: Vec3) {
Vec3.add(tmpVec, center, dirMajor)
Mat4.targetTo(m, center, tmpVec, dirMinor)
Mat4.setTranslation(m, center)
return Mat4.scale(m, m, radiusScale)
}
export function addEllipsoid(state: MeshBuilder.State, center: Vec3, dirMajor: Vec3, dirMinor: Vec3, radiusScale: Vec3, detail: number) {
MeshBuilder.addPrimitive(state, setEllipsoidMat(tmpEllipsoidMat, center, dirMajor, dirMinor, radiusScale), getSphere(detail))
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ChunkedArray } from '../../../../mol-data/util';
import { MeshBuilder } from '../mesh-builder';
const tA = Vec3.zero()
const tB = Vec3.zero()
const tV = Vec3.zero()
const horizontalVector = Vec3.zero()
const verticalVector = Vec3.zero()
const normalOffset = Vec3.zero()
const positionVector = Vec3.zero()
const normalVector = Vec3.zero()
const torsionVector = Vec3.zero()
/** set arrowHeight = 0 for no arrow */
export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number) {
const { currentGroup, vertices, normals, indices, groups } = state
let vertexCount = vertices.elementCount
let offsetLength = 0
if (arrowHeight > 0) {
Vec3.fromArray(tA, controlPoints, 0)
Vec3.fromArray(tB, controlPoints, linearSegments * 3)
offsetLength = arrowHeight / Vec3.magnitude(Vec3.sub(tV, tB, tA))
}
for (let i = 0; i <= linearSegments; ++i) {
const width = widthValues[i]
const height = heightValues[i]
const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
const i3 = i * 3
Vec3.fromArray(verticalVector, normalVectors, i3)
Vec3.scale(verticalVector, verticalVector, actualHeight);
Vec3.fromArray(horizontalVector, binormalVectors, i3)
Vec3.scale(horizontalVector, horizontalVector, width);
if (arrowHeight > 0) {
Vec3.fromArray(tA, normalVectors, i3)
Vec3.fromArray(tB, binormalVectors, i3)
Vec3.scale(normalOffset, Vec3.cross(normalOffset, tA, tB), offsetLength)
}
Vec3.fromArray(positionVector, controlPoints, i3)
Vec3.fromArray(normalVector, normalVectors, i3)
Vec3.fromArray(torsionVector, binormalVectors, i3)
Vec3.add(tA, positionVector, verticalVector)
Vec3.negate(tB, torsionVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
Vec3.sub(tA, positionVector, verticalVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
Vec3.add(tA, positionVector, verticalVector)
Vec3.copy(tB, torsionVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
Vec3.sub(tA, positionVector, verticalVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
}
for (let i = 0; i < linearSegments; ++i) {
ChunkedArray.add3(
indices,
vertexCount + i * 4,
vertexCount + (i + 1) * 4 + 1,
vertexCount + i * 4 + 1
);
ChunkedArray.add3(
indices,
vertexCount + i * 4,
vertexCount + (i + 1) * 4,
vertexCount + (i + 1) * 4 + 1
);
ChunkedArray.add3(
indices,
vertexCount + i * 4 + 2 + 1,
vertexCount + (i + 1) * 4 + 2 + 1,
vertexCount + i * 4 + 2
);
ChunkedArray.add3(
indices,
vertexCount + i * 4 + 2,
vertexCount + (i + 1) * 4 + 2 + 1,
vertexCount + (i + 1) * 4 + 2
);
}
const addedVertexCount = (linearSegments + 1) * 4
for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup)
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 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>
@@ -71,7 +71,7 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi
}
/** set arrowHeight = 0 for no arrow */
export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) {
export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number, startCap: boolean, endCap: boolean) {
const { currentGroup, vertices, normals, indices, groups } = state
let vertexCount = vertices.elementCount
@@ -84,6 +84,9 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
}
for (let i = 0; i <= linearSegments; ++i) {
const width = widthValues[i]
const height = heightValues[i]
const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
const i3 = i * 3
@@ -141,32 +144,55 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
}
for (let i = 0; i < linearSegments; ++i) {
for (let j = 0; j < 4; j++) {
// the triangles are arranged such that opposing triangles of the sheet align
// which prevents triangle intersection within tight curves
for (let j = 0; j < 2; j++) {
ChunkedArray.add3(
indices,
vertexCount + i * 8 + 2 * j,
vertexCount + (i + 1) * 8 + 2 * j + 1,
vertexCount + i * 8 + 2 * j + 1
vertexCount + i * 8 + 2 * j, // a
vertexCount + (i + 1) * 8 + 2 * j + 1, // c
vertexCount + i * 8 + 2 * j + 1 // b
);
ChunkedArray.add3(
indices,
vertexCount + i * 8 + 2 * j,
vertexCount + (i + 1) * 8 + 2 * j,
vertexCount + (i + 1) * 8 + 2 * j + 1
vertexCount + i * 8 + 2 * j, // a
vertexCount + (i + 1) * 8 + 2 * j, // d
vertexCount + (i + 1) * 8 + 2 * j + 1 // c
);
}
for (let j = 2; j < 4; j++) {
ChunkedArray.add3(
indices,
vertexCount + i * 8 + 2 * j, // a
vertexCount + (i + 1) * 8 + 2 * j, // d
vertexCount + i * 8 + 2 * j + 1, // b
);
ChunkedArray.add3(
indices,
vertexCount + (i + 1) * 8 + 2 * j, // d
vertexCount + (i + 1) * 8 + 2 * j + 1, // c
vertexCount + i * 8 + 2 * j + 1, // b
);
}
}
if (startCap) {
const width = widthValues[0]
const height = heightValues[0]
const h = arrowHeight === 0 ? height : arrowHeight
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h)
} else if (arrowHeight > 0) {
const width = widthValues[0]
const height = heightValues[0]
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height)
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height)
}
if (endCap && arrowHeight === 0) {
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height)
const width = widthValues[linearSegments]
const height = heightValues[linearSegments]
// use negative height to flip the direction the cap's triangles are facing
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, -height, -height)
}
const addedVertexCount = (linearSegments + 1) * 8 +

View File

@@ -16,7 +16,7 @@ function setSphereMat(m: Mat4, center: Vec3, radius: number) {
return Mat4.scaleUniformly(m, Mat4.fromTranslation(m, center), radius)
}
function getSphere(detail: number) {
export function getSphere(detail: number) {
let sphere = sphereMap.get(detail)
if (sphere === undefined) {
sphere = Sphere(detail)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 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>
@@ -50,7 +50,15 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
const t = 2 * Math.PI * j / radialSegments;
add3AndScale2(surfacePoint, u, v, controlPoint, h * Math.cos(t), w * Math.sin(t))
add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
if (radialSegments === 2) {
// add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
Vec3.copy(normalVector, v)
console.log(i, t)
Vec3.normalize(normalVector, normalVector)
if (t !== 0 || i % 2 === 0) Vec3.negate(normalVector, normalVector)
} else {
add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
}
Vec3.normalize(normalVector, normalVector)
ChunkedArray.add3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);

View File

@@ -101,9 +101,9 @@ export namespace MeshBuilder {
}
}
export function addCage(state: State, t: Mat4, cage: Cage, radius: number, detail: number) {
export function addCage(state: State, t: Mat4, cage: Cage, radius: number, detail: number, radialSegments: number) {
const { vertices: va, edges: ea } = cage
const cylinderProps = { radiusTop: radius, radiusBottom: radius }
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments }
for (let i = 0, il = ea.length; i < il; i += 2) {
Vec3.fromArray(tmpVecA, va, ea[i] * 3)
Vec3.fromArray(tmpVecB, va, ea[i + 1] * 3)

View File

@@ -24,7 +24,8 @@ export function applyOverpaintColor(array: Uint8Array, start: number, end: numbe
}
export function clearOverpaint(array: Uint8Array, start: number, end: number) {
array.fill(0, start, end)
array.fill(0, start * 4, end * 4)
return true
}
export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {

View File

@@ -89,7 +89,7 @@ export namespace Spheres {
const padding = getMaxSize(size)
const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
spheres.centerBuffer.ref.value, spheres.sphereCount * 4,
transform.aTransform.ref.value, instanceCount, padding
transform.aTransform.ref.value, instanceCount, padding, 4
)
return {
@@ -130,7 +130,7 @@ export namespace Spheres {
const padding = getMaxSize(values)
const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
values.aPosition.ref.value, spheres.sphereCount * 4,
values.aTransform.ref.value, values.instanceCount.ref.value, padding
values.aTransform.ref.value, values.instanceCount.ref.value, padding, 4
)
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere)

View File

@@ -13,7 +13,7 @@ import { Theme } from '../../../mol-theme/theme';
import { createColors } from '../color-data';
import { createSizes, getMaxSize } from '../size-data';
import { createMarkers } from '../marker-data';
import { ColorNames } from '../../../mol-util/color/tables';
import { ColorNames } from '../../../mol-util/color/names';
import { Sphere3D } from '../../../mol-math/geometry';
import { calculateBoundingSphere, TextureImage, createTextureImage } from '../../../mol-gl/renderable/util';
import { TextValues } from '../../../mol-gl/renderable/text';

View File

@@ -4,6 +4,9 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3 } from '../../mol-math/linear-algebra'
import { NumberArray } from '../../mol-util/type-helpers'
export interface Cage {
readonly vertices: ArrayLike<number>
readonly edges: ArrayLike<number>
@@ -11,4 +14,24 @@ export interface Cage {
export function createCage(vertices: ArrayLike<number>, edges: ArrayLike<number>): Cage {
return { vertices, edges }
}
export function copyCage(cage: Cage): Cage {
return {
vertices: new Float32Array(cage.vertices),
edges: new Uint32Array(cage.edges)
}
}
const tmpV = Vec3.zero()
/** Transform primitive in-place */
export function transformCage(cage: Cage, t: Mat4) {
const { vertices } = cage
for (let i = 0, il = vertices.length; i < il; i += 3) {
// position
Vec3.transformMat4(tmpV, Vec3.fromArray(tmpV, vertices, i), t)
Vec3.toArray(tmpV, vertices as NumberArray, i)
}
return cage
}

View File

@@ -8,16 +8,18 @@
* Create 3d points for a polygon:
* 3 for a triangle, 4 for a rectangle, 5 for a pentagon, 6 for a hexagon...
*/
export function polygon(sideCount: number, shift: boolean) {
export function polygon(sideCount: number, shift: boolean, radius = -1) {
const points = new Float32Array(sideCount * 3)
const radius = sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6
const r = radius === -1
? (sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6)
: radius
const offset = shift ? 1 : 0
for (let i = 0, il = sideCount; i < il; ++i) {
const c = (i * 2 + offset) / sideCount * Math.PI
points[i * 3] = Math.cos(c) * radius
points[i * 3 + 1] = Math.sin(c) * radius
points[i * 3] = Math.cos(c) * r
points[i * 3 + 1] = Math.sin(c) * r
points[i * 3 + 2] = 0
}
return points

View File

@@ -1,10 +1,12 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3 } from '../../mol-math/linear-algebra';
import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
import { getNormalMatrix } from '../util';
import { NumberArray } from '../../mol-util/type-helpers';
export interface Primitive {
vertices: ArrayLike<number>
@@ -28,6 +30,14 @@ export function createPrimitive(vertices: ArrayLike<number>, indices: ArrayLike<
return builder.getPrimitive()
}
export function copyPrimitive(primitive: Primitive): Primitive {
return {
vertices: new Float32Array(primitive.vertices),
normals: new Float32Array(primitive.normals),
indices: new Uint32Array(primitive.indices)
}
}
export interface PrimitiveBuilder {
add(a: Vec3, b: Vec3, c: Vec3): void
getPrimitive(): Primitive
@@ -56,4 +66,22 @@ export function PrimitiveBuilder(triangleCount: number): PrimitiveBuilder {
},
getPrimitive: () => ({ vertices, normals, indices })
}
}
const tmpV = Vec3.zero()
const tmpMat3 = Mat3.zero()
/** Transform primitive in-place */
export function transformPrimitive(primitive: Primitive, t: Mat4) {
const { vertices, normals } = primitive
const n = getNormalMatrix(tmpMat3, t)
for (let i = 0, il = vertices.length; i < il; i += 3) {
// position
Vec3.transformMat4(tmpV, Vec3.fromArray(tmpV, vertices, i), t)
Vec3.toArray(tmpV, vertices as NumberArray, i)
// normal
Vec3.transformMat3(tmpV, Vec3.fromArray(tmpV, normals, i), n)
Vec3.toArray(tmpV, normals as NumberArray, i)
}
return primitive
}

View File

@@ -9,26 +9,39 @@ import { Primitive, PrimitiveBuilder } from './primitive';
import { polygon } from './polygon'
import { Cage } from './cage';
const on = Vec3.create(0, 0, -0.5), op = Vec3.create(0, 0, 0.5)
const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
const on = Vec3(), op = Vec3()
const a = Vec3(), b = Vec3(), c = Vec3(), d = Vec3()
export const DefaultPrismProps = {
height: 1,
topCap: true,
bottomCap: true,
}
export type PrismProps = Partial<typeof DefaultPrismProps>
/**
* Create a prism with a base of 4 or more points
*/
export function Prism(points: ArrayLike<number>): Primitive {
export function Prism(points: ArrayLike<number>, props?: PrismProps): Primitive {
const sideCount = points.length / 3
if (sideCount < 4) throw new Error('need at least 4 points to build a prism')
const { height, topCap, bottomCap } = { ...DefaultPrismProps, ...props };
const count = 4 * sideCount
const builder = PrimitiveBuilder(count)
const halfHeight = height * 0.5
Vec3.set(on, 0, 0, -halfHeight)
Vec3.set(op, 0, 0, halfHeight)
// create sides
for (let i = 0; i < sideCount; ++i) {
const ni = (i + 1) % sideCount
Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5)
Vec3.set(c, points[ni * 3], points[ni * 3 + 1], 0.5)
Vec3.set(d, points[i * 3], points[i * 3 + 1], 0.5)
Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight)
Vec3.set(c, points[ni * 3], points[ni * 3 + 1], halfHeight)
Vec3.set(d, points[i * 3], points[i * 3 + 1], halfHeight)
builder.add(a, b, c)
builder.add(c, d, a)
}
@@ -36,12 +49,16 @@ export function Prism(points: ArrayLike<number>): Primitive {
// create bases
for (let i = 0; i < sideCount; ++i) {
const ni = (i + 1) % sideCount
Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5)
builder.add(on, b, a)
Vec3.set(a, points[i * 3], points[i * 3 + 1], 0.5)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], 0.5)
builder.add(a, b, op)
if (topCap) {
Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight)
builder.add(on, b, a)
}
if (bottomCap) {
Vec3.set(a, points[i * 3], points[i * 3 + 1], halfHeight)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], halfHeight)
builder.add(a, b, op)
}
}
return builder.getPrimitive()
@@ -61,29 +78,42 @@ export function PentagonalPrism() {
let hexagonalPrism: Primitive
export function HexagonalPrism() {
if (!hexagonalPrism) hexagonalPrism = Prism(polygon(6, true))
if (!hexagonalPrism) hexagonalPrism = Prism(polygon(6, false))
return hexagonalPrism
}
let shiftedHexagonalPrism: Primitive
export function ShiftedHexagonalPrism() {
if (!shiftedHexagonalPrism) shiftedHexagonalPrism = Prism(polygon(6, true))
return shiftedHexagonalPrism
}
let heptagonalPrism: Primitive
export function HeptagonalPrism() {
if (!heptagonalPrism) heptagonalPrism = Prism(polygon(7, false))
return heptagonalPrism
}
//
/**
* Create a prism cage
*/
export function PrismCage(points: ArrayLike<number>): Cage {
export function PrismCage(points: ArrayLike<number>, height = 1): Cage {
const sideCount = points.length / 3
// const count = 4 * sideCount
const vertices: number[] = []
const edges: number[] = []
const halfHeight = height * 0.5
let offset = 0
// vertices and side edges
for (let i = 0; i < sideCount; ++i) {
vertices.push(
points[i * 3], points[i * 3 + 1], -0.5,
points[i * 3], points[i * 3 + 1], 0.5
points[i * 3], points[i * 3 + 1], -halfHeight,
points[i * 3], points[i * 3 + 1], halfHeight
)
edges.push(offset, offset + 1)
offset += 2
@@ -115,6 +145,6 @@ export function PentagonalPrismCage() {
let hexagonalPrismCage: Cage
export function HexagonalPrismCage() {
if (!hexagonalPrismCage) hexagonalPrismCage = PrismCage(polygon(6, true))
if (!hexagonalPrismCage) hexagonalPrismCage = PrismCage(polygon(6, false))
return hexagonalPrismCage
}

View File

@@ -54,6 +54,12 @@ export function Pyramid(points: ArrayLike<number>): Primitive {
return builder.getPrimitive()
}
let triangularPyramid: Primitive
export function TriangularPyramid() {
if (!triangularPyramid) triangularPyramid = Pyramid(polygon(3, true))
return triangularPyramid
}
let octagonalPyramid: Primitive
export function OctagonalPyramid() {
if (!octagonalPyramid) octagonalPyramid = Pyramid(polygon(8, true))

View File

@@ -28,11 +28,9 @@ import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-dat
function createRenderer(gl: WebGLRenderingContext) {
const ctx = createContext(gl)
const camera = new Camera({
near: 0.01,
far: 10000,
position: Vec3.create(0, 0, 50)
})
const renderer = Renderer.create(ctx, camera)
const renderer = Renderer.create(ctx)
return { ctx, camera, renderer }
}
@@ -117,7 +115,7 @@ describe('renderer', () => {
expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[3]).toBe(48)
})
it('points', () => {
it('points', async () => {
const [ width, height ] = [ 32, 32 ]
const gl = createGl(width, height, { preserveDrawingBuffer: true })
const { ctx } = createRenderer(gl)
@@ -126,6 +124,7 @@ describe('renderer', () => {
const points = createPoints()
scene.add(points)
await scene.commit().run()
expect(ctx.stats.bufferCount).toBe(4);
expect(ctx.stats.textureCount).toBe(5);
expect(ctx.stats.vaoCount).toBe(5);
@@ -133,6 +132,7 @@ describe('renderer', () => {
expect(ctx.shaderCache.count).toBe(10);
scene.remove(points)
await scene.commit().run()
expect(ctx.stats.bufferCount).toBe(0);
expect(ctx.stats.textureCount).toBe(0);
expect(ctx.stats.vaoCount).toBe(0);

View File

@@ -159,6 +159,7 @@ export const GlobalUniformSchema = {
uPixelRatio: UniformSpec('f'),
uViewportHeight: UniformSpec('f'),
uViewport: UniformSpec('v4'),
uViewOffset: UniformSpec('v2'),
uCameraPosition: UniformSpec('v3'),
uNear: UniformSpec('f'),
@@ -167,7 +168,9 @@ export const GlobalUniformSchema = {
uFogFar: UniformSpec('f'),
uFogColor: UniformSpec('v3'),
uTransparentBackground: UniformSpec('i'),
uPickingAlphaThreshold: UniformSpec('f'),
uInteriorDarkening: UniformSpec('f'),
}
export type GlobalUniformSchema = typeof GlobalUniformSchema
export type GlobalUniformValues = Values<GlobalUniformSchema> // { [k in keyof GlobalUniformSchema]: ValueCell<any> }

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -80,14 +80,15 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
const v = Vec3.zero()
const boundaryHelper = new BoundaryHelper()
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number): Sphere3D {
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D {
const step = stepFactor * 3
boundaryHelper.reset(0)
for (let i = 0, _i = positionCount * 3; i < _i; i += 3) {
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
boundaryHelper.boundaryStep(v, 0)
}
boundaryHelper.finishBoundaryStep()
for (let i = 0, _i = positionCount * 3; i < _i; i += 3) {
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
boundaryHelper.extendStep(v, 0)
}
@@ -109,8 +110,8 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
return boundaryHelper.getSphere()
}
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {
const invariantBoundingSphere = calculateInvariantBoundingSphere(position, positionCount)
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {
const invariantBoundingSphere = calculateInvariantBoundingSphere(position, positionCount, stepFactor)
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform, transformCount)
Sphere3D.expand(boundingSphere, boundingSphere, padding)
Sphere3D.expand(invariantBoundingSphere, invariantBoundingSphere, padding)

View File

@@ -9,7 +9,7 @@ import { Camera } from '../mol-canvas3d/camera';
import Scene from './scene';
import { WebGLContext } from './webgl/context';
import { Mat4, Vec3, Vec4 } from '../mol-math/linear-algebra';
import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra';
import { Renderable } from './renderable';
import { Color } from '../mol-util/color';
import { ValueCell } from '../mol-util';
@@ -38,53 +38,59 @@ interface Renderer {
readonly props: Readonly<RendererProps>
clear: () => void
render: (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => void
render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => void
setProps: (props: Partial<RendererProps>) => void
setViewport: (x: number, y: number, width: number, height: number) => void
dispose: () => void
}
export const RendererParams = {
backgroundColor: PD.Color(Color(0x000000)),
backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
transparentBackground: PD.Boolean(false, { description: 'Background opacity of the 3D canvas' }),
pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
roughness: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
}
export type RendererProps = PD.Values<typeof RendererParams>
namespace Renderer {
export function create(ctx: WebGLContext, camera: Camera, props: Partial<RendererProps> = {}): Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats } = ctx
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
const viewport = Viewport()
const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor)
const view = Mat4.clone(camera.view)
const invView = Mat4.invert(Mat4.identity(), view)
const modelView = Mat4.clone(camera.view)
const invModelView = Mat4.invert(Mat4.identity(), modelView)
const invProjection = Mat4.invert(Mat4.identity(), camera.projection)
const modelViewProjection = Mat4.mul(Mat4.identity(), modelView, camera.projection)
const invModelViewProjection = Mat4.invert(Mat4.identity(), modelViewProjection)
const view = Mat4()
const invView = Mat4()
const modelView = Mat4()
const invModelView = Mat4()
const invProjection = Mat4()
const modelViewProjection = Mat4()
const invModelViewProjection = Mat4()
const viewOffset = Vec2()
const globalUniforms: GlobalUniformValues = {
uModel: ValueCell.create(Mat4.identity()),
uView: ValueCell.create(camera.view),
uView: ValueCell.create(view),
uInvView: ValueCell.create(invView),
uModelView: ValueCell.create(modelView),
uInvModelView: ValueCell.create(invModelView),
uInvProjection: ValueCell.create(invProjection),
uProjection: ValueCell.create(Mat4.clone(camera.projection)),
uProjection: ValueCell.create(Mat4()),
uModelViewProjection: ValueCell.create(modelViewProjection),
uInvModelViewProjection: ValueCell.create(invModelViewProjection),
uIsOrtho: ValueCell.create(camera.state.mode === 'orthographic' ? 1 : 0),
uIsOrtho: ValueCell.create(1),
uViewOffset: ValueCell.create(viewOffset),
uPixelRatio: ValueCell.create(ctx.pixelRatio),
uViewportHeight: ValueCell.create(viewport.height),
uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
@@ -96,14 +102,16 @@ namespace Renderer {
uRoughness: ValueCell.create(p.roughness),
uReflectivity: ValueCell.create(p.reflectivity),
uCameraPosition: ValueCell.create(Vec3.clone(camera.state.position)),
uNear: ValueCell.create(camera.state.near),
uFar: ValueCell.create(camera.state.far),
uFogNear: ValueCell.create(camera.state.fogNear),
uFogFar: ValueCell.create(camera.state.fogFar),
uCameraPosition: ValueCell.create(Vec3()),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
uFogNear: ValueCell.create(1),
uFogFar: ValueCell.create(10000),
uFogColor: ValueCell.create(bgColor),
uTransparentBackground: ValueCell.create(p.transparentBackground ? 1 : 0),
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
uInteriorDarkening: ValueCell.create(p.interiorDarkening),
}
const globalUniformList = Object.entries(globalUniforms)
@@ -153,7 +161,7 @@ namespace Renderer {
}
}
const render = (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => {
const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => {
ValueCell.update(globalUniforms.uModel, scene.view)
ValueCell.update(globalUniforms.uView, camera.view)
ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view))
@@ -165,12 +173,13 @@ namespace Renderer {
ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection))
ValueCell.update(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0)
ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0))
ValueCell.update(globalUniforms.uCameraPosition, camera.state.position)
ValueCell.update(globalUniforms.uFar, camera.state.far)
ValueCell.update(globalUniforms.uNear, camera.state.near)
ValueCell.update(globalUniforms.uFogFar, camera.state.fogFar)
ValueCell.update(globalUniforms.uFogNear, camera.state.fogNear)
ValueCell.update(globalUniforms.uFar, camera.far)
ValueCell.update(globalUniforms.uNear, camera.near)
ValueCell.update(globalUniforms.uFogFar, camera.fogFar)
ValueCell.update(globalUniforms.uFogNear, camera.fogNear)
globalUniformsNeedUpdate = true
state.currentRenderItemId = -1
@@ -185,7 +194,7 @@ namespace Renderer {
if (clear) {
if (variant === 'color') {
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0)
state.clearColor(bgColor[0], bgColor[1], bgColor[2], p.transparentBackground ? 0 : 1)
} else {
state.clearColor(1, 1, 1, 1)
}
@@ -198,7 +207,7 @@ namespace Renderer {
if (r.state.opaque) renderObject(r, variant)
}
state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE)
state.enable(gl.BLEND)
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i]
@@ -218,7 +227,7 @@ namespace Renderer {
clear: () => {
state.depthMask(true)
state.colorMask(true, true, true, true)
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0)
state.clearColor(bgColor[0], bgColor[1], bgColor[2], p.transparentBackground ? 0 : 1)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
},
render,
@@ -228,11 +237,19 @@ namespace Renderer {
p.pickingAlphaThreshold = props.pickingAlphaThreshold
ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold)
}
if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
p.interiorDarkening = props.interiorDarkening
ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening)
}
if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
p.backgroundColor = props.backgroundColor
Color.toVec3Normalized(bgColor, p.backgroundColor)
ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor))
}
if (props.transparentBackground !== undefined && props.transparentBackground !== p.transparentBackground) {
p.transparentBackground = props.transparentBackground
ValueCell.update(globalUniforms.uTransparentBackground, p.transparentBackground ? 1 : 0)
}
if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) {
p.lightIntensity = props.lightIntensity
ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,6 +12,8 @@ import { Object3D } from './object3d';
import { Sphere3D } from '../mol-math/geometry';
import { Vec3 } from '../mol-math/linear-algebra';
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
import { RuntimeContext, Task } from '../mol-task';
import { AsyncQueue } from '../mol-util/async-queue';
const boundaryHelper = new BoundaryHelper();
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
@@ -40,17 +42,11 @@ function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Rendera
const drawProgramIdB = b.getProgram('color').id
const materialIdA = a.materialId
const materialIdB = b.materialId
const zA = a.values.boundingSphere.ref.value.center[2]
const zB = b.values.boundingSphere.ref.value.center[2]
if (drawProgramIdA !== drawProgramIdB) {
return drawProgramIdA - drawProgramIdB // sort by program id to minimize gl state changes
} else if (materialIdA !== materialIdB) {
return materialIdA - materialIdB // sort by material id to minimize gl state changes
} else if (zA !== zB) {
return a.state.opaque
? zA - zB // when opaque, draw closer elements first to minimize overdraw
: zB - zA // when transparent, draw elements last to maximize partial visibility
} else {
return a.id - b.id;
}
@@ -60,10 +56,13 @@ interface Scene extends Object3D {
readonly count: number
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
readonly boundingSphere: Sphere3D
readonly isCommiting: boolean
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
add: (o: GraphicsRenderObject) => Renderable<any>
add: (o: GraphicsRenderObject) => void // Renderable<any>
remove: (o: GraphicsRenderObject) => void
syncCommit: () => void
commit: () => Task<void>
has: (o: GraphicsRenderObject) => boolean
clear: () => void
forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
@@ -74,15 +73,63 @@ namespace Scene {
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
const renderables: Renderable<RenderableValues & BaseValues>[] = []
const boundingSphere = Sphere3D.zero()
let boundingSphereDirty = true
const object3d = Object3D.create()
const add = (o: GraphicsRenderObject) => {
if (!renderableMap.has(o)) {
const renderable = createRenderable(ctx, o)
renderables.push(renderable)
renderableMap.set(o, renderable)
boundingSphereDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
return renderableMap.get(o)!
}
}
const remove = (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o)
if (renderable) {
renderable.dispose()
renderables.splice(renderables.indexOf(renderable), 1)
renderableMap.delete(o)
boundingSphereDirty = true
}
}
const commitQueue = new AsyncQueue<any>();
const toAdd: GraphicsRenderObject[] = []
const toRemove: GraphicsRenderObject[] = []
type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] }
const step = 100
const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => {
for (let i = 0, il = arr.length; i < il; i += step) {
if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il })
for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) {
fn(arr[j])
}
}
}
const commit = async (ctx: RuntimeContext, p: CommitParams) => {
await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects')
await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects')
if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' })
renderables.sort(renderableSort)
}
return {
get view () { return object3d.view },
get position () { return object3d.position },
get direction () { return object3d.direction },
get up () { return object3d.up },
get isCommiting () { return commitQueue.length > 0 },
update(objects, keepBoundingSphere) {
Object3D.update(object3d)
@@ -100,27 +147,35 @@ namespace Scene {
if (!keepBoundingSphere) boundingSphereDirty = true
},
add: (o: GraphicsRenderObject) => {
if (!renderableMap.has(o)) {
const renderable = createRenderable(ctx, o)
renderables.push(renderable)
renderables.sort(renderableSort)
renderableMap.set(o, renderable)
boundingSphereDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
return renderableMap.get(o)!
}
toAdd.push(o)
},
remove: (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o)
if (renderable) {
renderable.dispose()
renderables.splice(renderables.indexOf(renderable), 1)
renderables.sort(renderableSort)
renderableMap.delete(o)
boundingSphereDirty = true
}
toRemove.push(o)
},
syncCommit: () => {
for (let i = 0, il = toRemove.length; i < il; ++i) remove(toRemove[i])
toRemove.length = 0
for (let i = 0, il = toAdd.length; i < il; ++i) add(toAdd[i])
toAdd.length = 0
renderables.sort(renderableSort)
},
commit: () => {
const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
toAdd.length = 0
toRemove.length = 0
return Task.create('Commiting GraphicsRenderObjects', async ctx => {
const removed = await commitQueue.enqueue(params);
if (!removed) return;
try {
await commit(ctx, params);
} finally {
commitQueue.handled(params);
}
}, () => {
commitQueue.remove(params);
})
},
has: (o: GraphicsRenderObject) => {
return renderableMap.has(o)

View File

@@ -2,10 +2,11 @@ export default `
#ifdef dUseFog
float depth = length(vViewPosition);
float fogFactor = smoothstep(uFogNear, uFogFar, depth);
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
if (fogAlpha < 0.01)
discard;
gl_FragColor = vec4(gl_FragColor.rgb, fogAlpha);
if (uTransparentBackground == 0) {
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
} else {
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
gl_FragColor.a = fogAlpha;
}
#endif
`

View File

@@ -36,7 +36,7 @@ geometry.normal = normal;
geometry.viewDir = normalize(vViewPosition);
IncidentLight directLight;
directLight.direction = geometry.viewDir;
directLight.direction = vec3(0.0, 0.0, -1.0);
directLight.color = vec3(uLightIntensity);
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);

View File

@@ -13,6 +13,12 @@ export default `
#endif
#endif
#if defined(dColorType_uniform) || defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
if (gl_FrontFacing == false) {
material.rgb *= 1.0 - uInteriorDarkening;
}
#endif
// mix material with overpaint
#if defined(dOverpaint) && (defined(dColorType_uniform) || defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance))
material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a);
@@ -23,6 +29,9 @@ export default `
float ta = 1.0 - vTransparency;
float at = 0.0;
// shift by view-offset during multi-sample rendering to allow for blending
vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25;
#if defined(dTransparencyVariant_single)
const mat4 thresholdMatrix = mat4(
1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
@@ -30,9 +39,9 @@ export default `
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
);
at = thresholdMatrix[int(intMod(gl_FragCoord.x, 4.0))][int(intMod(gl_FragCoord.y, 4.0))];
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
#elif defined(dTransparencyVariant_multi)
at = fract(dot(vec3(gl_FragCoord.xy, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
#endif
if (ta < 0.99 && (ta < 0.01 || ta < at)) discard;

View File

@@ -5,10 +5,16 @@ uniform int uGroupCount;
uniform vec3 uHighlightColor;
uniform vec3 uSelectColor;
varying float vMarker;
#if __VERSION__ != 300
varying float vMarker;
#else
flat in float vMarker;
#endif
varying vec3 vViewPosition;
uniform vec2 uViewOffset;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;
@@ -16,4 +22,7 @@ uniform vec3 uFogColor;
uniform float uAlpha;
uniform float uPickingAlphaThreshold;
uniform int uPickable;
uniform int uTransparentBackground;
uniform float uInteriorDarkening;
`

View File

@@ -8,7 +8,11 @@ uniform int uGroupCount;
uniform vec2 uMarkerTexDim;
uniform sampler2D tMarker;
varying float vMarker;
#if __VERSION__ != 300
varying float vMarker;
#else
flat out float vMarker;
#endif
varying vec3 vViewPosition;
`

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