Compare commits

...

555 Commits

Author SHA1 Message Date
dsehnal
f833efae37 2.2.2 2021-08-11 14:54:03 +02:00
dsehnal
be4b787e66 Fix mol-script query compiler const expression recognition 2021-08-11 14:49:52 +02:00
David Sehnal
950b1c179a Merge pull request #248 from MadCatX/fix-isosurface
Do not cache LevelTexturesFramebuffers as they may become invalid
2021-08-10 12:40:52 +02:00
Michal Malý
2bd1a01afb Do not attach framebuffer unnecessarily 2021-08-10 09:15:05 +02:00
Michal Malý
9d34dbff0f Do not cache LevelTexturesFramebuffers as they may become invalid 2021-08-09 09:58:55 +02:00
Alexander Rose
ba1b03f01b fix TransformData issues, see #133
- handle structure vs structure.root in ExplodeStructureRepresentation3D and SpinStructureRepresentation3D
2021-08-08 13:11:50 -07:00
Alexander Rose
791f7ca3c8 Merge pull request #245 from sukolsak/optimize-setCylinderMat 2021-08-08 10:45:23 -07:00
Sukolsak Sakshuwong
c14d50e4ff optimize setCylinderMat() 2021-08-08 08:51:16 -07:00
dsehnal
fc2765d376 2.2.1 2021-08-02 18:11:24 +02:00
dsehnal
9d85194082 2.2.1 changelog 2021-08-02 18:09:50 +02:00
David Sehnal
abfcc60898 Merge pull request #243 from molstar/input-observer-improvements
Input observer improvements
2021-08-02 18:08:48 +02:00
dsehnal
c688a83fa2 fix typo 2021-08-02 18:07:34 +02:00
dsehnal
77376056b9 changelog 2021-08-02 18:03:07 +02:00
dsehnal
8efd943c2b PinchInput.fractionDelta 2021-08-02 17:24:49 +02:00
dsehnal
b230655439 fix type 2021-08-02 16:55:06 +02:00
dsehnal
8ba792c4b0 add maxWheelDelta 2021-08-02 16:50:09 +02:00
dsehnal
fde8ca69e4 support for Safari gestures (pinch zoom on MacBook trackpad) 2021-08-02 16:30:48 +02:00
dsehnal
9ee1439299 normalize wheel speed in input observer 2021-08-02 14:45:25 +02:00
David Sehnal
195668760e Merge pull request #242 from sukolsak/export-overpaint
Add overpaint support to geometry exporters
2021-08-02 13:04:09 +02:00
Sukolsak Sakshuwong
bd64f1db9a update changelog and fix type 2021-08-02 01:19:31 -07:00
Sukolsak Sakshuwong
38a5a857aa refactor color calculation 2021-08-01 23:09:30 -07:00
Sukolsak Sakshuwong
5e8cdfe3a7 add overpaint support to geometry exporters 2021-08-01 22:30:05 -07:00
Alexander Rose
738b7f4ca5 Merge pull request #238 from molstar/dynbonds
Bond improvements (mostly IndexPairBonds)
2021-08-01 13:53:42 -07:00
Alexander Rose
bce53d03a5 bond tweaks
- add DefaultBondMaxRadius constant
- add IndexPairBonds.Props object
2021-08-01 13:49:44 -07:00
Alexander Rose
74f721ab9f improve Structure.asParent
- handle parent coordinate system not identity
2021-08-01 13:48:26 -07:00
Alexander Rose
23cf5c2fdd changelog and docs 2021-07-31 14:22:45 -07:00
Alexander Rose
e211abd5ae Merge branch 'master' of https://github.com/molstar/molstar into dynbonds 2021-07-31 14:11:45 -07:00
Alexander Rose
7199be4d62 tweak getColorSmoothingProps
- make independent of webgl context
2021-07-31 14:10:50 -07:00
Alexander Rose
e1a40ded1d add surronding atoms structure selection query 2021-07-31 13:53:49 -07:00
Alexander Rose
8999c3097d handle dynamicBonds in root structure helper 2021-07-31 13:50:07 -07:00
Alexander Rose
44308fa1fd add maxDistance prop to IndexPairBonds 2021-07-31 13:48:10 -07:00
Alexander Rose
f5dd2f4579 support coordinateSystem in structure.asParent 2021-07-31 13:45:39 -07:00
dsehnal
104999b7dc 2.2.0 2021-07-31 15:15:09 +02:00
dsehnal
e5341623d3 changelog v2.2.0 2021-07-31 15:12:40 +02:00
dsehnal
0e9238e5ec Canvas3D tweaks:
- update "forceDraw" logic
- Ensure the scene is re-rendered when viewport size changes
- Support noDraw mode in PluginAnimationLoop
2021-07-31 15:06:58 +02:00
dsehnal
43c292e2df Support new EMDB API for EM volume contour levels 2021-07-31 14:12:33 +02:00
dsehnal
fbfd1b20d8 Prefer _label_seq_id fields in secondary structure assignment 2021-07-31 13:54:11 +02:00
dsehnal
5330df87e1 Merge branch 'master' of https://github.com/molstar/molstar 2021-07-27 12:29:17 +02:00
dsehnal
ad6b3c6fe0 add DS_store to .gitignore 2021-07-27 12:28:56 +02:00
Alexander Rose
add76a87d9 remove unnecessary check
- see 7686b61728
2021-07-25 20:22:00 -07:00
Alexander Rose
f9f8350d28 dynamic pair bonds on coordinate changes
- add dynamicBonds structure parameter
- add maxRadius, ignoreWater bond compute parameters
- ensure inter unit bond visuals are recreated
2021-07-24 17:16:59 -07:00
Alexander Rose
b71c2f365c add operator Loci granularity 2021-07-24 16:55:07 -07:00
Alexander Rose
a5443189d3 missing param 2021-07-24 16:46:30 -07:00
Alexander Rose
7686b61728 fix includeParent for multi instance bond visuals 2021-07-24 16:31:26 -07:00
Alexander Rose
844c13cd35 2.2.0-dev.1 2021-07-20 21:08:51 -07:00
Alexander Rose
d1c8b92fdf Merge pull request #224 from sukolsak/usdz-export
USDZ export
2021-07-18 09:28:27 -07:00
Sukolsak Sakshuwong
93d33bca80 view USDZ in AR on iOS 2021-07-17 21:41:26 -07:00
Sukolsak Sakshuwong
6550e53414 Merge branch 'master' into usdz-export 2021-07-17 18:50:43 -07:00
Alexander Rose
96dddb0998 updated changelog 2021-07-17 10:54:34 -07:00
Alexander Rose
baa64d8109 handle mononucleotides when guessing component type 2021-07-17 10:42:56 -07:00
David Sehnal
2df145aa8f Merge pull request #231 from molstar/sdf-parser-improvements
Sdf parser improvements
2021-07-16 18:27:16 +02:00
dsehnal
06b9c5f2de change SDF data header parsing
- do not trim <> around field
- store whole line staring with '> ' as data header (without the staring '> ')
2021-07-16 18:25:22 +02:00
dsehnal
e03b689f27 add SdfFormat 2021-07-16 18:04:25 +02:00
Sukolsak Sakshuwong
e4cdcff3ee Merge branch 'master' into usdz-export 2021-07-12 00:11:24 -07:00
Alexander Rose
f73150d074 Merge pull request #225 from molstar/tubular-helices
Add tubularHelices parameter to Cartoon representation
2021-07-11 11:45:38 -07:00
Alexander Rose
451dc12689 cleanup 2021-07-11 11:31:02 -07:00
Alexander Rose
a3fb7762d8 add tubular helices to Cartoon representation 2021-07-10 15:41:54 -07:00
Alexander Rose
3dfafc3202 handle cell angles close to zero in dcd reader 2021-07-10 15:35:53 -07:00
Alexander Rose
4fcea991d3 set default outline scale back to 1 2021-07-10 15:28:49 -07:00
Alexander Rose
0607ed46d1 handle more common ff residue/atom names 2021-07-10 15:28:23 -07:00
Sukolsak Sakshuwong
30d6244e82 add support for USDZ 2021-07-10 05:19:42 -07:00
Sukolsak Sakshuwong
fab8c74365 move quantizeColors to MeshExporter 2021-07-09 13:10:09 -07:00
Sukolsak Sakshuwong
1952922e4e make RenderObjectExporter.getData async 2021-07-09 13:10:09 -07:00
Alexander Rose
1eb351369e 2.1.0 2021-07-05 16:11:46 -07:00
Alexander Rose
701d782485 changelog 2021-07-05 16:07:30 -07:00
Alexander Rose
dc8457c4dc smoother trace normals 2021-07-05 15:59:28 -07:00
Alexander Rose
f104cd4d11 add missing import 2021-07-05 13:47:39 -07:00
Alexander Rose
9b56a6ae65 Merge pull request #221 from molstar/aromatic
Aromatic bond display option
2021-07-05 13:42:11 -07:00
Alexander Rose
2485ad5a2f Merge branch 'master' into aromatic 2021-07-05 13:38:05 -07:00
Alexander Rose
a56716ab6a Merge pull request #222 from molstar/backbone
Backbone representation
2021-07-05 13:37:24 -07:00
Alexander Rose
e0aaaa989e Merge branch 'master' into backbone 2021-07-05 13:34:40 -07:00
Alexander Rose
9ec0f9e736 outline fixes and improvements
- better handle outlines in orthographic mode
- remove unused code
- increase default outline scale to 2
2021-07-05 13:31:00 -07:00
Alexander Rose
47968eeeec fix traceOnly handling in makeElementIgnoreTest 2021-07-04 14:54:52 -07:00
Alexander Rose
9c157b70e1 warning for arrayAreIntersecting/arrayIntersectionSize 2021-07-04 14:50:58 -07:00
Alexander Rose
6d7e4ca227 remove unused import 2021-07-04 14:48:06 -07:00
Alexander Rose
fccd08d2ec add backbone repr
- atomistic and coarse units
2021-07-04 14:39:56 -07:00
Alexander Rose
19bae202d0 handle Vec3.angle edge case 2021-07-04 14:35:46 -07:00
Alexander Rose
4ba0ae24e4 support aromatic bond display as dashes
- add arrayAreIntersecting/arrayIntersectionSize helpers
- prefer reference atoms within rings (also for double/triple bonds)
2021-07-03 23:12:06 -07:00
Alexander Rose
fcf3718d75 better parsing of mol2 bond types 2021-07-03 22:38:29 -07:00
Alexander Rose
df1dd94f1c fix BondType.Names 2021-07-03 22:35:35 -07:00
Alexander Rose
65ba401850 fix repr update for Structure.asParent objects 2021-07-03 22:26:20 -07:00
Alexander Rose
a98f5e1047 fix fxaa antialiasing
- was broken when used with other postprocessing effects
- expose texture.filter
2021-07-03 22:19:51 -07:00
David Sehnal
e5cf97d1ea Merge pull request #217 from sukolsak/fix-cylinder
Fix cylinder orientation
2021-06-28 14:45:38 +02:00
Sukolsak Sakshuwong
1844fc14b2 fix cylinder orientation 2021-06-27 13:14:30 -07:00
dsehnal
d185c0ef34 2.0.7 2021-06-23 12:44:20 +02:00
dsehnal
40a4211e75 fix CIF text encoder edge cases & added test 2021-06-23 12:41:23 +02:00
dsehnal
daa2bbd042 Merge branch 'master' of https://github.com/molstar/molstar 2021-06-21 16:39:25 +02:00
Alexander Rose
ed5b4b27a8 guard against atom_site not being available 2021-06-20 22:55:25 -07:00
Alexander Rose
408ccb4353 fix bond cylinder imposter update issue 2021-06-20 13:54:15 -07:00
Alexander Rose
99e3cd6654 fix image export issues
- handle pre-multiplied alpha
- don't clear draw target unless written to
2021-06-20 13:51:58 -07:00
Alexander Rose
0819ace1dc use CustomProperty.Provider.ref 2021-06-20 13:49:42 -07:00
dsehnal
987c9210bd In-place reordering support for Frame.x/y/z 2021-06-19 12:26:42 +02:00
dsehnal
84fb42a161 Support volumeIndex in Viewer.loadVolumeFromUrl 2021-06-19 11:24:32 +02:00
dsehnal
53d3480701 fix isConnectedTo query 2021-06-15 17:53:40 +02:00
David Sehnal
eb629ef337 Merge pull request #212 from sukolsak/center-export
Geometry export: center exported models
2021-06-14 15:55:16 +02:00
Sukolsak Sakshuwong
c26111e8fb center exported models 2021-06-13 23:32:00 -07:00
dsehnal
4853ff7a1a fix volume streaming channel visibility 2021-06-11 15:19:04 +02:00
dsehnal
1bdebda136 2.0.6 2021-06-01 18:49:51 +02:00
dsehnal
fe5b847797 2.0.6 changelog 2021-06-01 18:47:51 +02:00
dsehnal
19ec5b226c changelog 2021-06-01 12:43:11 +02:00
dsehnal
4bb32d31dc support atom id list in selection helper 2021-06-01 12:41:04 +02:00
Alexander Rose
976a469cc7 Merge pull request #199 from molstar/original-mesh-data
Better handling of processed meshes
2021-05-31 10:46:35 -07:00
Alexander Rose
86087aa3ca Merge pull request #203 from sukolsak/export-original-mesh-data
Geometry export: use original mesh data
2021-05-31 10:34:22 -07:00
Sukolsak Sakshuwong
c0e955d472 export original mesh data 2021-05-30 06:25:43 -07:00
Alexander Rose
eca052e52e fix entity-source color showing black
- fixes #172
- now always using light-grey/white for regions with unkown source
2021-05-29 15:20:27 -07:00
Alexander Rose
a1e05387e4 add Mesh.getOriginalData accessor 2021-05-29 15:01:02 -07:00
Alexander Rose
301940c8bd fix canvas not cleared
- #201
- happens e.g. with antialiasing disabled plus transparent background on
2021-05-29 14:26:11 -07:00
Alexander Rose
d96303627c keep some original data after mesh processing
- processing in uniformTriangleGroup
- to be used in, e.g., geometry export
2021-05-28 23:43:37 -07:00
Alexander Rose
051b48776e consider BB atoms as trace 2021-05-28 23:37:25 -07:00
Alexander Rose
26054681d8 color smoothing param tweaks 2021-05-28 23:36:59 -07:00
Alexander Rose
70fa85d7d4 fix assembly-symmetry off option
- would not remove cage/axes visuals
2021-05-28 23:04:08 -07:00
Alexander Rose
5a23cd483e Merge pull request #198 from sukolsak/export-auto-quality
Geometry export: auto adjust quality of sphere/cylinder meshes
2021-05-28 22:57:19 -07:00
Alexander Rose
d759b07f1b Merge pull request #193 from JonStargaryen/anvil-fixes
ANVIL fixes
2021-05-28 22:52:49 -07:00
Sukolsak Sakshuwong
4694da0057 auto adjust quality of sphere/cylinder meshes 2021-05-28 11:05:06 -07:00
JonStargaryen
f930e3dbe0 avoiid cast 2021-05-28 08:43:05 -07:00
Alexander Rose
fcf45d20be Merge pull request #197 from sukolsak/gltf-material
Geometry export: add material to GLB
2021-05-27 22:53:45 -07:00
JonStargaryen
ad4ba7bcf9 drop redundant ops 2021-05-27 16:15:06 -07:00
JonStargaryen
26644ede49 no need to reassign neighbors 2021-05-27 15:41:48 -07:00
JonStargaryen
810973ff54 cleanup 2021-05-27 14:07:40 -07:00
JonStargaryen
6ad09c60c0 cleanup 2021-05-27 14:01:40 -07:00
JonStargaryen
dc146f5f04 Eisenhaber 1995 improvements 2021-05-27 12:38:35 -07:00
JonStargaryen
e1b771bba4 use existing impl 2021-05-27 09:48:31 -07:00
Sukolsak Sakshuwong
e2ab3a6fd6 add material to glb 2021-05-27 07:33:28 -07:00
JonStargaryen
d1296de676 bs on ordered array 2021-05-26 14:11:31 -07:00
JonStargaryen
fcac1a62c6 sample only 1 hemisphere 2021-05-26 12:54:03 -07:00
JonStargaryen
5eafddf97a fine-grained updates for large structures 2021-05-26 12:02:04 -07:00
JonStargaryen
e2dcbc3d65 const 2021-05-26 11:26:28 -07:00
JonStargaryen
54a388da9c store hphobhphil stats 2021-05-26 09:58:58 -07:00
JonStargaryen
3849c341b8 no optional chaining 2021-05-26 09:30:06 -07:00
JonStargaryen
31f4803c0a no array copies 2021-05-26 09:21:42 -07:00
JonStargaryen
d6e36d4ca7 cleanup 2021-05-25 14:55:49 -07:00
JonStargaryen
0d526fdc98 drop location in some more places 2021-05-25 14:35:19 -07:00
JonStargaryen
04b36170d8 cleanup 2021-05-25 13:15:36 -07:00
JonStargaryen
db787c9ea4 avoid location in filter loop 2021-05-25 12:38:43 -07:00
JonStargaryen
e1e6f9ca48 wip membership 2021-05-25 11:55:22 -07:00
JonStargaryen
40b5605e10 local Vec3 methods 2021-05-25 08:57:08 -07:00
JonStargaryen
609654b689 dont attach ASA values 2021-05-25 08:39:50 -07:00
dsehnal
45ef00f1d1 Move FileHandle.fromDescriptor to /servers to avoid importing 'fs' in browser builds 2021-05-25 17:18:47 +02:00
dsehnal
88380ff917 fix production and debug flags 2021-05-25 16:22:58 +02:00
dsehnal
bc7bfe9788 fix webpack watch build 2021-05-25 16:09:40 +02:00
dsehnal
469ca6cb41 fix webpack build 2021-05-25 15:51:40 +02:00
David Sehnal
c0be790ff1 Merge pull request #195 from MadCatX/update_deps
Update node-sass and webpack dependencies
2021-05-25 15:24:21 +02:00
David Sehnal
8c1d16353e Merge pull request #196 from molstar/sequence-mapping
Basic sequence mapping support
2021-05-25 15:20:49 +02:00
dsehnal
d76d475015 BestDatabaseSequenceMapping superposition 2021-05-25 15:15:33 +02:00
dsehnal
69024152cb Best Database Sequence Mapping property
- assigned based on atom_site.db_name/_acc/_num/_res CIF fields
- added basic color theme
2021-05-25 13:20:35 +02:00
Michal Malý
4a19aedec8 Update node-sass and webpack dependencies 2021-05-24 21:50:25 +02:00
JonStargaryen
df89351301 remove normal vector 2021-05-24 12:24:23 -07:00
JonStargaryen
9a0c87695f cleanup 2021-05-24 11:45:38 -07:00
JonStargaryen
a393231522 inline x/y/z 2021-05-24 10:00:54 -07:00
Alexander Rose
33de60d365 fix typeof check 2021-05-22 22:49:13 -07:00
JonStargaryen
3cf67f7605 fix coloring for trace-only ASA calc 2021-05-22 19:48:02 -07:00
Alexander Rose
ffdcf798e0 Merge pull request #194 from sukolsak/fix-smaa
Fix HTMLImageElement check for SMAA
2021-05-22 14:19:59 -07:00
Sukolsak Sakshuwong
397e1235e7 fix HTMLImageElement check for SMAA 2021-05-22 02:58:59 -07:00
JonStargaryen
4e77699076 more fine-grained status messages 2021-05-21 21:35:57 -07:00
JonStargaryen
b47d046505 mapping still needed for #132 2021-05-21 15:31:49 -07:00
JonStargaryen
74aa24bfa0 traceOnly: check for BB as well 2021-05-21 09:27:02 -07:00
JonStargaryen
30d5b0ddb1 rename to traceOnly 2021-05-21 09:24:09 -07:00
Alexander Rose
1e35ea15eb Merge pull request #192 from sukolsak/gltf-instancing
Geometry export: use instancing in GLB
2021-05-20 23:47:52 -07:00
Sukolsak Sakshuwong
bc998ab328 don't split triangles in Mesh.uniformTriangleGroup
Calling Mesh.uniformTriangleGroup for WebGL1 is required for picking to work properly. With color smoothing implemented, we don't have to split triangles in Mesh.uniformTriangleGroup anymore. This should help reduce the exported file size in WebGL1. This change is suggested by @arose
2021-05-20 17:03:48 -07:00
JonStargaryen
e5e245f4ee scale radius used for evaluation 2021-05-20 14:26:42 -07:00
JonStargaryen
c6073b894a ASA coloring wrong after removing part of a chain #132 2021-05-20 11:38:04 -07:00
Sukolsak Sakshuwong
9b11794f22 store colors in glb using 8 bits per channel 2021-05-20 09:57:59 -07:00
JonStargaryen
f2b9dceaab ANVIL runtime updates 2021-05-20 09:16:19 -07:00
Sukolsak Sakshuwong
9ccaaf6c80 cleanup 2021-05-20 07:20:56 -07:00
Sukolsak Sakshuwong
ecb97e525e omit indices from texture-mesh in glb 2021-05-20 04:44:13 -07:00
dsehnal
c36c6a6d97 support nested Lookup3D queries
- fixes non-covalent interactions bug
2021-05-20 09:34:59 +02:00
JonStargaryen
60b92471f1 cleanup 2021-05-19 19:34:06 -07:00
JonStargaryen
79e283cfbd wip ANVIL debugging 2021-05-19 15:22:16 -07:00
Sukolsak Sakshuwong
3778dacb08 min and max only required for vertex arrays 2021-05-18 22:27:58 -07:00
Sukolsak Sakshuwong
e407f7279b add generator info to glb 2021-05-18 22:25:37 -07:00
Sukolsak Sakshuwong
ea54209414 gltf instancing 2021-05-18 19:48:59 -07:00
JonStargaryen
d10a36509b ignore non-canonical aa 2021-05-18 15:03:48 -07:00
JonStargaryen
4af560e63a visuals cleanup 2021-05-18 12:23:55 -07:00
JonStargaryen
ecb8900258 wip adjust thickness 2021-05-18 09:49:50 -07:00
Alexander Rose
7bfc1b0ebc sreenshot tweaks
- don't turn of screenspace antialiasing
- use higher numbr of AO samples than default
2021-05-16 12:04:00 -07:00
Alexander Rose
5edae9d6f7 Merge pull request #173 from molstar/smcol
Color smoothing
2021-05-16 11:39:44 -07:00
Alexander Rose
fe702a8c63 fix high resolution molecular surface artefacts
- adjust probePositions prop with quality
2021-05-16 11:33:03 -07:00
Alexander Rose
c8868464a5 color smoothing param tweaks
- reduce sample stride to avoid artefacts
- min color grid resolution for better smoothing at high mesh resolution
2021-05-16 11:31:57 -07:00
Alexander Rose
720e65d2e6 Merge pull request #189 from sukolsak/volume-color
Geometry export: support volume & volumeInstance color types
2021-05-16 10:39:34 -07:00
Sukolsak Sakshuwong
b5123ff36a fix drawCount 2021-05-16 05:33:58 -07:00
Sukolsak Sakshuwong
d237034e8e quantize colors when exporting OBJ 2021-05-16 00:28:31 -07:00
Sukolsak Sakshuwong
aab95d27e0 convert sRGB to linear when exporting GLB 2021-05-15 23:56:50 -07:00
Sukolsak Sakshuwong
c68306125e support volume & volumeInstance color type in geo exporters 2021-05-15 23:55:21 -07:00
Alexander Rose
3173396737 use constant from gl context if available 2021-05-15 11:19:30 -07:00
Alexander Rose
212a3eeb6c tweaked auto color smoothing props 2021-05-15 11:18:39 -07:00
Alexander Rose
17b25354f5 added isPromiseLike helper
- use instead of instanceof Promise
2021-05-15 11:16:32 -07:00
JonStargaryen
9f176bd2bc add alpha-oonly option to asa calc 2021-05-12 12:20:28 -07:00
Alexander Rose
4a78283ce1 changelog 2021-05-09 21:10:43 -07:00
Alexander Rose
81e29533dc Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-05-09 21:09:19 -07:00
Alexander Rose
c1a2c602a1 remove unfinished dColorGridType support 2021-05-09 21:08:19 -07:00
Alexander Rose
c436653ce9 made transparency helpers a bit more generic 2021-05-09 14:35:00 -07:00
Alexander Rose
a2b4ed7c1c queries: add nos-bridge, improve disulfide-bridge 2021-05-09 13:56:40 -07:00
Alexander Rose
83968aa408 cylinder mesh builder improvements 2021-05-09 13:09:00 -07:00
dsehnal
71539cc75a citation in readme 2021-05-09 11:53:16 +02:00
dsehnal
881d4d2a99 Fix IndexPairBonds for structures with re-ordered atoms 2021-05-09 11:46:20 +02:00
David Sehnal
ae2f2e7d0e Merge pull request #181 from molstar/spin-struct-anim
Spin structure animation
2021-05-09 10:47:14 +02:00
Alexander Rose
e31f0f7660 basic spin-structure animation 2021-05-08 23:54:27 -07:00
Alexander Rose
3586207968 better SO autobonding 2021-05-08 19:55:36 -07:00
Alexander Rose
b575793b83 Merge pull request #180 from sukolsak/fix-sheet-caps
Fix normals in sheet caps
2021-05-08 18:06:30 -07:00
Alexander Rose
81bf653790 Merge pull request #179 from sukolsak/fix-arrow
Fix normals in sheet arrows
2021-05-08 18:03:30 -07:00
Sukolsak Sakshuwong
6186c60cd9 fix normals in sheet caps 2021-05-08 00:03:42 -07:00
Sukolsak Sakshuwong
6ab480589a fix lint 2021-05-07 23:55:53 -07:00
Sukolsak Sakshuwong
571f8187c3 fix normals in sheet arrows 2021-05-07 23:29:35 -07:00
Alexander Rose
d510ff00dc Merge pull request #177 from sukolsak/per-group-transparency
Add per-group transparency support to geometry exporters
2021-05-06 23:10:08 -07:00
Sukolsak Sakshuwong
7a0f286fb4 add per-group transparency support to geo-export 2021-05-05 17:54:27 -07:00
Alexander Rose
fccf8d6b87 Merge pull request #174 from sukolsak/glb-export
GLB and STL export
2021-05-04 22:22:41 -07:00
Sukolsak Sakshuwong
e0c08e89d0 use more descriptive labels for geometry exporters 2021-05-04 20:40:28 -07:00
Sukolsak Sakshuwong
ef9885411c small cleanup 2021-05-04 07:45:16 -07:00
Sukolsak Sakshuwong
7542ead360 address @arose's comments 2021-05-03 23:57:32 -07:00
Sukolsak Sakshuwong
043ab08066 fix normal vectors being zero 2021-05-03 18:11:36 -07:00
Sukolsak Sakshuwong
ec0933d197 update changelog 2021-05-03 12:20:20 -07:00
Sukolsak Sakshuwong
aef34a687d glb and stl export 2021-05-03 12:19:28 -07:00
Alexander Rose
9b7192f261 color smoothing for element-index theme 2021-05-03 01:02:34 -07:00
Alexander Rose
18212d9ee7 cpu trilinear interpolation for color smoothing 2021-05-03 00:48:14 -07:00
Alexander Rose
8d4e0730e8 improved color smoothing params handling 2021-05-02 23:34:19 -07:00
Alexander Rose
ba8e9e189f auto-smooth colors if preferred 2021-05-01 18:59:06 -07:00
Alexander Rose
b7935de7af limit color smoothing to group-based themes 2021-05-01 18:57:42 -07:00
Alexander Rose
10ca32f9d7 volumetric color smoothing
- for Mesh and TextureMesh geometries
2021-05-01 18:29:30 -07:00
Alexander Rose
bb86d83c96 better granularity in element-symbol color theme 2021-05-01 17:48:35 -07:00
Alexander Rose
907b08cc99 wip, color grid shader support
- only tested for mesh
2021-05-01 17:47:53 -07:00
Alexander Rose
a07d593909 tweak printImageData debug helper 2021-05-01 17:43:43 -07:00
Alexander Rose
a0a3ff1969 Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-05-01 17:42:52 -07:00
dsehnal
7fac8a8f77 2.0.5 2021-04-26 16:02:31 +02:00
dsehnal
7266c67e32 2.0.5 changelog 2021-04-26 16:00:31 +02:00
dsehnal
50c8d09742 default camera radius set to 0 2021-04-26 15:13:46 +02:00
dsehnal
7377947975 Changelog 2021-04-25 15:15:26 +02:00
David Sehnal
a3c4daf30a Merge pull request #169 from sukolsak/texture-mesh-export
Add TextureMesh export support
2021-04-25 15:13:26 +02:00
Sukolsak Sakshuwong
9d7e6f1d99 add TextureMesh export support 2021-04-25 05:04:03 -07:00
dsehnal
9e105020e3 lazy volume loading 2021-04-25 12:10:09 +02:00
Alexander Rose
698f7e16bd Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-04-24 09:42:34 -07:00
Alexander Rose
93df548cfe add torus primitive and fix render tests 2021-04-23 22:55:20 -07:00
Alexander Rose
a0b1593c82 add MeshBuilder.addMesh 2021-04-23 22:12:19 -07:00
Alexander Rose
fc81e08d73 Support full pausing (no draw) rendering 2021-04-23 22:10:01 -07:00
Alexander Rose
5369fa5adf canvas viewport support fixes and improvements
- restrict ssao to viewport bounds
- only downscale ssao buffer (not upscale)
- avoid zero camera radius/radiusMax to allow camera movements in empty scenes and to avoid ssao artifacts
2021-04-23 22:07:34 -07:00
dsehnal
316a77c716 guard against non-invertible matrices in Camera.update
+ relative viewports and dynamic updating of them sometimes caused non-invertible matrix
2021-04-23 19:05:29 +02:00
dsehnal
42dfa69ad7 Residue list selection helper 2021-04-21 21:24:28 +02:00
dsehnal
cae4eb8b0e await screenshot clipboard write & fallback to <img> on fail 2021-04-21 20:08:37 +02:00
David Sehnal
5514b24fdf Merge pull request #167 from molstar/multi-canvas3d
Multi-canvas support for PluginContext
2021-04-21 20:05:05 +02:00
dsehnal
d570bc352e "relative" canvas3d viewport and picking dimensions fix 2021-04-20 20:26:13 +02:00
dsehnal
8a76a3fa64 2.0.4 2021-04-20 11:23:23 +02:00
dsehnal
71bf4e21f5 changelog 2021-04-20 11:21:29 +02:00
dsehnal
e0d36c30d3 Fix measurement labels & interactions 2021-04-20 11:19:15 +02:00
Alexander Rose
d653a96b25 Merge branch 'master' of https://github.com/molstar/molstar into smcol 2021-04-18 13:12:12 -07:00
David Sehnal
b53debcfef Merge pull request #163 from sukolsak/fix-zip-progress
Fix incorrect deflate progress
2021-04-14 15:02:15 +02:00
Sukolsak Sakshuwong
d0705ac226 fix deflate progress 2021-04-14 05:07:05 -07:00
Alexander Rose
e01eacb3fe changelog 2021-04-13 23:12:39 -07:00
Alexander Rose
d4102b476b Fix, read SDF multi-line values 2021-04-13 23:07:55 -07:00
dsehnal
83ce17174a changelog 2021-04-13 21:17:28 +02:00
dsehnal
18023d7f26 Merge branch 'master' of https://github.com/molstar/molstar 2021-04-13 21:16:28 +02:00
dsehnal
a8541d5967 Structure.eachAtomicHierarchyElement 2021-04-13 21:15:44 +02:00
David Sehnal
8b21818f2e Merge pull request #159 from sukolsak/obj-export
OBJ Export
2021-04-13 17:06:48 +02:00
Sukolsak Sakshuwong
0b290247dc show partial progress when exporting large meshes 2021-04-11 13:15:50 -07:00
Sukolsak Sakshuwong
fb5010e962 reorder arguments to addMeshWithColors() 2021-04-10 18:12:32 -07:00
Sukolsak Sakshuwong
178789d327 make RenderObjectExporter.add() async 2021-04-10 18:10:42 -07:00
Sukolsak Sakshuwong
4fae526073 wip 2021-04-09 15:59:14 -07:00
dsehnal
05f1d8085a 2.0.3 2021-04-09 17:47:12 +02:00
dsehnal
38bbabd742 fix test 2021-04-09 17:45:51 +02:00
dsehnal
3ab958a93c changelog 2021-04-09 17:43:23 +02:00
dsehnal
f59d589a30 CubeGridFormat 2021-04-09 17:41:13 +02:00
David Sehnal
11f7e54704 Merge pull request #158 from molstar/gradient-themes
Add ColorTheme.palette support
2021-04-09 10:01:59 +02:00
dsehnal
16ebd8266e changelog 2021-04-09 10:01:21 +02:00
dsehnal
7a796a4d3d Merge branch 'master' of https://github.com/molstar/molstar into gradient-themes 2021-04-09 09:59:46 +02:00
Alexander Rose
1cbb915962 started a changelog 2021-04-08 23:14:55 -07:00
Alexander Rose
80486d58c3 async deflate (and zip) 2021-04-08 23:13:30 -07:00
dsehnal
81bc116c4d TextureImage.filter 2021-04-08 09:49:35 +02:00
dsehnal
4249064dd1 Add ColorTheme.palette support
- add example to basic-wrapper that uses it
2021-04-07 15:55:54 +02:00
Alexander Rose
e0a594121b wip, color smoothing experiments 2021-04-06 23:36:29 -07:00
dsehnal
028c02f50d 2.0.2 2021-03-29 12:48:26 +02:00
dsehnal
76e97d7b59 fix VolumeFromDensityServerCif label 2021-03-29 12:46:10 +02:00
dsehnal
ad1181a75b interpolate ModelFromTrajectory transform 2021-03-29 12:03:13 +02:00
Alexander Rose
5d683462fb add common non-standard amino-acids 2021-03-28 15:38:15 -07:00
Alexander Rose
42422bb0ea add canvas3d.getRenderObjects 2021-03-28 15:36:40 -07:00
dsehnal
861e5c3e97 SDF data item test 2021-03-24 16:02:53 +01:00
dsehnal
614cffda96 2.0.1 2021-03-23 15:44:24 +01:00
dsehnal
2e0379d202 npm ignore 2021-03-23 15:42:52 +01:00
dsehnal
b5cfdcd2a3 2.0.0 2021-03-23 15:31:18 +01:00
dsehnal
c00de6fde0 Merge branch 'master' of https://github.com/molstar/molstar 2021-03-23 11:16:26 +01:00
Alexander Rose
da3a8e56f3 handle negative isovalues in gpu mc 2021-03-22 22:06:49 -07:00
dsehnal
103d6fe775 alpha orbitals tryUseGpu param 2021-03-22 20:18:48 +01:00
dsehnal
5df55e6bf7 SDF delimiter bugfix + multi-molecule SDF support in mol-plugin 2021-03-22 17:21:47 +01:00
dsehnal
3b285086d4 rename files called "macro" due to Jest not being able to process them 2021-03-22 16:53:48 +01:00
dsehnal
91793bc3cc 2.0.0-dev.13 2021-03-22 13:29:42 +01:00
dsehnal
fa3828e820 add model-server-query transform support 2021-03-22 12:51:03 +01:00
Alexander Rose
31ba8212da 2.0.0-dev.12 2021-03-21 16:47:53 -07:00
Alexander Rose
fe27d8e134 Merge pull request #150 from molstar/stubs2
basic support for bond stubs
2021-03-21 16:40:03 -07:00
Alexander Rose
83dcdfdc4b Merge commit '2faa821c50a6dfce700eb8072a61d01d937c18e5' into stubs2 2021-03-21 16:36:33 -07:00
Alexander Rose
f9aaabc1f7 fix interactions bounding sphere 2021-03-21 16:29:56 -07:00
Alexander Rose
034370b44c add includeParent support to interactions 2021-03-21 16:25:03 -07:00
Alexander Rose
b87666df3e don't pad empty bounding spheres 2021-03-21 16:24:25 -07:00
Alexander Rose
c98c3228fe fix structure.asParent 2021-03-21 16:23:57 -07:00
Alexander Rose
9419980dfc make structure state private (like before) 2021-03-21 12:39:21 -07:00
Alexander Rose
42d60420e5 added Structure.asParent
- refactored structure state handling
- removed Structure.WithChild
2021-03-21 12:10:24 -07:00
dsehnal
5b1df333a7 tsconfig jsx param 2021-03-21 16:18:01 +01:00
Alexander Rose
0bb376706d fix ellipsoid repr and support includeParent
- switch off adjustCylinderLength
- handle structure with child
2021-03-20 23:58:48 -07:00
Alexander Rose
eca7da2c72 add adjustCylinderLength param
- so it can be switched off
2021-03-20 23:54:50 -07:00
Alexander Rose
b0bdb3ddb6 tweak param help 2021-03-20 23:52:23 -07:00
Alexander Rose
3180d7c305 basic support for bond stubs
- line and ball & stick repr
- stubs support in link visual helper
- getData and mustRecreate methods for structure repr provider
- Structure.WithChild helper (needs Proxy support)
2021-03-20 18:05:58 -07:00
dsehnal
2faa821c50 2.0.0-dev.11 2021-03-19 17:29:29 +01:00
David Sehnal
7f355ae501 Merge pull request #141 from molstar/surrounding-ligands
Surrounding Ligands query
2021-03-19 17:16:25 +01:00
dsehnal
7f79ff9ff2 StructureSourceControls: show hierarchy preset is >1 trajectory is selected 2021-03-18 15:29:48 +01:00
dsehnal
02de871c59 StructureBoundingBox3D transform 2021-03-18 15:18:15 +01:00
dsehnal
00cb783d4c BoxShape3D transform 2021-03-18 14:15:04 +01:00
David Sehnal
c925919ee5 Merge pull request #148 from TomasKulhanek/master
FIX issue #147 CSS transform:scale cause molstar canvas to have incorrect size
2021-03-17 10:50:44 +01:00
dsehnal
324820890a Fix createModelProperty.isApplicable 2021-03-17 10:35:29 +01:00
Tomas Kulhanek
2687b29d4d FIX molstar/molstar#147 offsetWidth/offsetHeight is correct size of element when css transform:scale is used 2021-03-17 07:46:41 +00:00
dsehnal
7084aaee1a adjust text 2021-03-16 23:02:14 +01:00
dsehnal
520a2f7850 model-server: empty result console output 2021-03-16 22:47:34 +01:00
Alexander Rose
9264987817 camera helper tweaks
- add highlighting
- improved axes alignment
2021-03-15 23:16:19 -07:00
dsehnal
b736ed3ea4 readme tweaks 2021-03-15 21:35:17 +01:00
dsehnal
166d660fa7 2.0.0-dev.10 2021-03-15 20:20:21 +01:00
dsehnal
b8249cde4d interactive camera axis helper 2021-03-15 20:16:07 +01:00
dsehnal
f12f5eca90 Merge branch 'master' into surrounding-ligands 2021-03-15 16:45:53 +01:00
dsehnal
cd3798b46f disable SwaggerUI response syntax highlight 2021-03-15 16:44:36 +01:00
dsehnal
0240e54737 TrackballControlsParams.autoAdjustControls 2021-03-15 14:13:11 +01:00
dsehnal
6a735d902e fix XYZ parser bug 2021-03-15 13:31:44 +01:00
dsehnal
57a942ecb5 requestCameraReset SnapshotProvide
- allow to customize the snapshop based on the current scane/boundingbox/camera state
2021-03-15 12:47:45 +01:00
dsehnal
f67605a398 applyMarkerAction fix 2 2021-03-14 18:46:04 +01:00
dsehnal
aaafa1d5ad model-server: surroundingLigands query 2021-03-14 15:09:37 +01:00
dsehnal
a1d9a77653 surroundingLigands query 2021-03-14 15:00:13 +01:00
dsehnal
f2f1181af3 Merge branch 'master' into surrounding-ligands 2021-03-14 13:16:22 +01:00
dsehnal
864befc48a applyMarkerAction fix 2021-03-14 13:09:53 +01:00
dsehnal
73f6793bd8 surroundingLigands query wip 2021-03-14 12:22:01 +01:00
dsehnal
87ee9d88f2 ResidueSet helper (wip) 2021-03-14 11:21:12 +01:00
dsehnal
b1e245e913 add UndirectedGraph 2021-03-14 10:19:28 +01:00
Alexander Rose
78c0471f39 remove unused Structure.unitsSortedByVolume 2021-03-13 22:35:24 -08:00
Alexander Rose
c57b9b9214 improve preset for many polymer gaps
- show all atom instead
- for medium sized structures
- fixes #57
2021-03-13 22:27:51 -08:00
Alexander Rose
34f33c5bbb fix apply marker type error 2021-03-13 22:24:48 -08:00
Alexander Rose
57da2a7ebb optimized applyMarkerAction
- extract switch statement out of loop
- use int32 view to handle 4 byte together
- don't check for change (essentially done at a higher level anyway)
2021-03-13 12:22:53 -08:00
Alexander Rose
d45d5c0e55 add assertUnreachable helper
- to type check if, e.g. if/switch statements are exhaustive
- TODO use...
2021-03-13 12:20:00 -08:00
dsehnal
42ed425e65 fix secondary_structure_type 2021-03-13 19:58:50 +01:00
dsehnal
f752ee5094 plugin-state server: remove /clear and can't remotely remove sticky entries 2021-03-13 17:56:18 +01:00
dsehnal
044c796942 Fix getSymmetryOperatorRef indexing 2021-03-13 17:53:42 +01:00
dsehnal
0aabbcfaab add back CreateVolumeStreamingBehavior custom controls 2021-03-13 16:40:48 +01:00
Alexander Rose
24274cc53b 2.0.0-dev.9 2021-03-09 22:50:34 -08:00
Alexander Rose
870cef2fd4 add collapse-left-panel to viewer query params 2021-03-09 22:46:53 -08:00
Alexander Rose
bf7b1f5bfd move StateActions to PluginSpec 2021-03-09 22:46:13 -08:00
Alexander Rose
9c9a0312db gpu mc attribution 2021-03-09 22:42:52 -08:00
dsehnal
724fa2a7cd package lock 2021-03-08 18:32:27 +01:00
Alexander Rose
19b36e5942 2.0.0-dev.8 2021-03-07 14:13:51 -08:00
Alexander Rose
b0dd9ab026 Merge pull request #135 from molstar/split-plugin-context
Move part of PluginContext to mol-plugin-ui
2021-03-07 13:41:17 -08:00
Alexander Rose
b77f1d4dee move initDataActions to PluginContext 2021-03-07 13:39:30 -08:00
dsehnal
3770fd7706 move actions back to PluginSpec 2021-03-07 13:36:50 +01:00
dsehnal
e3175c3ed1 Merge branch 'master' into split-plugin-context 2021-03-07 13:24:17 +01:00
dsehnal
7c5dd5b15b fix build caused by some typing edge case 2021-03-07 11:41:56 +01:00
Alexander Rose
0872e11669 2.0.0-dev.7 2021-03-07 01:29:34 -08:00
Alexander Rose
a66da4defc fix missing vars 2021-03-07 01:27:16 -08:00
Alexander Rose
d4ba13a2f2 2.0.0-dev.6 2021-03-07 01:22:30 -08:00
Alexander Rose
3b25e037aa remove obsolete viewer query params 2021-03-07 01:18:37 -08:00
Alexander Rose
189fad3d84 better handle focus on structure update
- fixes #123
2021-03-07 00:30:35 -08:00
Alexander Rose
c3c22ee3bc fix typos in xyz format 2021-03-06 23:39:12 -08:00
Alexander Rose
8a3222005c fix calculated label_seq_id 2021-03-06 23:38:21 -08:00
Alexander Rose
a17da36410 coarse grained tweaks
- coarse grained if less than three times as many atoms as polymer residues
- don't try dssp if coarse grained
2021-03-06 23:37:48 -08:00
Alexander Rose
80323d8122 2.0.0-dev.5 2021-03-06 13:59:17 -08:00
Alexander Rose
cbd6aa0b6b use 32bit depth texture in webgl2 2021-03-06 13:27:04 -08:00
Alexander Rose
3831bd9941 improve handling of coarse grained models 2021-03-06 11:17:43 -08:00
dsehnal
3d3e2c3a86 packages 2021-03-05 00:48:49 +01:00
dsehnal
acf13fa46f split plugin context (wip) 2021-03-05 00:33:00 +01:00
dsehnal
bc5d796653 make pairingThreshold slightly larger 2021-03-04 19:14:35 +01:00
dsehnal
82dd0496c2 covalentlyBondedComponent query 2021-03-04 18:53:05 +01:00
dsehnal
056742ac74 model index animation loop direction 2021-03-04 18:37:47 +01:00
dsehnal
29d4cfbcca add xyz support 2021-03-04 18:10:39 +01:00
Alexander Rose
376449f7c8 add missing PRO to standard components 2021-03-03 17:51:17 -08:00
Alexander Rose
bc37fad007 2.0.0-dev.4 2021-02-27 18:29:41 -08:00
Alexander Rose
2e561a8de7 added interesting pdb entry 2021-02-27 18:25:34 -08:00
Alexander Rose
e6c8c69d0c fix texture-mesh rendering artifacts
- added double buffering for texture-mesh textures
- added buffered uniforms
2021-02-27 18:25:06 -08:00
Alexander Rose
d121a11e28 tweaked structure-element export
- got rid of index.ts for better compatibility
2021-02-22 21:24:19 -08:00
dsehnal
5484a2a72c add logo to orbitals example 2021-02-22 18:56:03 +01:00
dsehnal
d527609b6d 2.0.0-dev.3 2021-02-21 19:23:06 +01:00
dsehnal
e628f580a7 add missing React key 2021-02-21 16:28:17 +01:00
Alexander Rose
b662179b4d improved lighting example 2021-02-20 01:59:46 -08:00
Alexander Rose
fa2b8542bf 2.0.0-dev.2 2021-02-18 21:20:14 -08:00
Alexander Rose
901522f500 added atom-id and entity-id color theme 2021-02-18 21:16:46 -08:00
dsehnal
62b63c1aa5 apply magic to solve GPU MC rendering issue 2021-02-18 19:05:07 +01:00
Alexander Rose
24b36f41da 2.0.0-dev.1 2021-02-15 22:09:05 -08:00
Alexander Rose
c9c890782c try re-use boundingSphere in element visuals
- if it has not changed much
2021-02-15 21:38:13 -08:00
Alexander Rose
f2c539ebd8 psf parser, support lammps "full" style 2021-02-15 18:04:38 -08:00
dsehnal
feb922ca91 Merge branch 'gpu' 2021-02-14 20:13:16 +01:00
dsehnal
25127bb84b Merge branch 'master' of https://github.com/molstar/molstar 2021-02-14 20:13:12 +01:00
dsehnal
8fb01d2157 Merge remote-tracking branch 'origin' into gpu 2021-02-14 20:11:11 +01:00
dsehnal
c09357ea75 updateImmediate for modelIndex 2021-02-14 20:00:37 +01:00
dsehnal
9f2513dae0 fix examples 2021-02-14 19:38:58 +01:00
dsehnal
11a52c0390 add missing TrajectoryInfo 2021-02-14 19:34:32 +01:00
dsehnal
e955dc7e94 exportable trajectory animation 2021-02-14 19:26:06 +01:00
Alexander Rose
c8107272f6 2.0.0-dev.0 2021-02-13 23:11:05 -08:00
Alexander Rose
fb08fe7545 remove extra files 2021-02-13 23:06:17 -08:00
Alexander Rose
b6f054ea28 Merge pull request #130 from molstar/lint-dep
Lint dep
2021-02-13 13:24:34 -08:00
Alexander Rose
dc7e85133c moved DefaultPluginSpec to spec.ts 2021-02-13 13:04:53 -08:00
Alexander Rose
90cddf4e41 allow named tuples
- beter use downlevel-dts for backwards compatibility
2021-02-13 13:00:11 -08:00
Alexander Rose
2cddbb72a6 fix tests, remove more deps from mat4, quat 2021-02-13 12:29:45 -08:00
Alexander Rose
a16faaac4e avoid some static dependencies
- those can lead to errors due to circular dependencies
- making them runtime dependencies fixes this
2021-02-13 12:06:07 -08:00
Alexander Rose
6c5224f33e new linting rules
- no default exports
- no named tuples
2021-02-13 11:36:21 -08:00
Alexander Rose
77d013b775 webgl, ensure active attribute with divisor 0
- workaround for FF <85
- needed for `texture-mesh` geometry
2021-02-13 11:30:13 -08:00
Alexander Rose
02a466e8b9 only update repr visibility when changed
- avoids superfluous scene rendering, e.g., when animating
2021-02-13 11:27:44 -08:00
Alexander Rose
3cb65cbe3d reduce ssao quality defaults a bit
- less texture fetches
2021-02-13 11:24:07 -08:00
Alexander Rose
fe8838542c 1.3.0 2021-02-07 13:29:46 -08:00
Alexander Rose
78b5c9aac4 Merge pull request #129 from molstar/gpu
Gpu acceleration for isosurface & gaussian-surface
2021-02-07 13:25:22 -08:00
Alexander Rose
021fa7b79b clearer param names for using gpu/impostors 2021-02-07 13:23:36 -08:00
Alexander Rose
0443589b09 allow gaussian volume without blendMinMax 2021-02-07 13:18:24 -08:00
Alexander Rose
415288de9f tweak quality settings
- lower resolution
2021-02-07 12:52:10 -08:00
Alexander Rose
ecbafb086a fix mc example 2021-02-06 12:46:25 -08:00
Alexander Rose
e5dae6c0dd increased default quality for larger structures
- rational is that larger structures can take a bit longer to create
2021-02-06 12:45:00 -08:00
Alexander Rose
16f4524bdb improved gpu support for representations
- enabled in volume isosurface & structure gaussian surface
- only if suitable (check memory requirements and resolution)
- falls back to cpu code
2021-02-06 12:43:24 -08:00
Alexander Rose
6b33021f43 fix webgl stats on render-item disposal 2021-02-06 12:30:12 -08:00
Alexander Rose
fdf37100c2 improved gpu-mc
- lower memory usage
- support for 2^32 vertices in webgl2
- fix rounding issue when creating volume texture
2021-02-06 12:28:52 -08:00
Alexander Rose
e28674d0dc model format improvements
- add a source format to the mmcif format
- add pdb format
- allow undefined in typeguard .is helpers
2021-02-03 19:36:01 -08:00
Alexander Rose
fb7456286a fix label_seq_id assignment when undefined column 2021-02-03 19:32:20 -08:00
Alexander Rose
9d240f8928 allow views of undefined columns 2021-02-03 19:30:14 -08:00
David Sehnal
48ef5efb21 Merge pull request #127 from molstar/pp-res
lower resolution ssao
2021-02-01 12:59:53 +01:00
Alexander Rose
52b2e7c144 lower res ssao 2021-01-31 13:22:22 -08:00
Alexander Rose
f2d1d60f6b fix cellpack & unit creation issues
- cellpack generate color theme can be applied to 1 model trajectories
- don't clone supplied props in Unit.create
- fix cellpack structure building to share unit.props
2021-01-31 12:06:40 -08:00
dsehnal
5a176a378a 1.2.15 2021-01-31 18:32:08 +01:00
dsehnal
60151c2c24 fix getUnitsSortedByVolume 2021-01-31 18:30:13 +01:00
dsehnal
a5db6350a2 only normalize Canvas3D props when loading from a saved state 2021-01-31 18:01:42 +01:00
dsehnal
0618eb18ba 1.2.14 2021-01-31 17:10:17 +01:00
Alexander Rose
bffdff6aad gaussian surface visual improvements
- add structure.unitsSortedByVolume
- increase gaussian smoothness in coarse presets
- use slice area instead of volume to ensure reasonable resolution
- use largest units (by volume) for reasonable resolution calculation
2021-01-30 22:55:29 -08:00
Alexander Rose
7753a6ec56 renderable schema cleanup
- use base schema in direct-volume schema
2021-01-30 12:57:19 -08:00
Alexander Rose
b8aafa1d78 mol-gl improvements
- int textures (webgl2)
- read into Int32Array (webgl2)
- fix ctx.parameters.maxDrawingBuffers (webgl1)
- support setting frag out type (webgl2)
2021-01-30 12:42:48 -08:00
Alexander Rose
672875187b add renderable.state.disposed flag
- set when disposing render-objects
- don't render disposed objects (can be temporarily still in a scene)
2021-01-30 12:39:33 -08:00
Alexander Rose
547d60d573 fix texture-mesh vertex count 2021-01-30 12:33:59 -08:00
Alexander Rose
99471d2a7b add xray shading edge fallof parameter 2021-01-30 12:32:20 -08:00
Alexander Rose
45d249b71a log renderItemId in debug mode 2021-01-30 11:23:46 -08:00
Alexander Rose
1382edd81c improved trackball rotate on wide canvases 2021-01-30 11:23:15 -08:00
dsehnal
89a6102f8d 1.2.13 2021-01-30 14:49:17 +01:00
David Sehnal
163929477e Merge pull request #106 from molstar/cylinders
Cylinders geomery and link visual improvements
2021-01-30 14:45:26 +01:00
dsehnal
c10a8369e8 Canvas3d: force render on viewport resize 2021-01-30 14:10:50 +01:00
dsehnal
8fbba52de8 PD.normalizeParams update 2021-01-30 13:41:22 +01:00
dsehnal
ca3174b2c3 Fix computeUnitGaussianDensity 2021-01-30 13:08:42 +01:00
dsehnal
b9864fba80 Fix loci label custom text 2021-01-27 18:01:47 +01:00
Alexander Rose
f8e9bc1e7f limit max resolution for (gpu) gaussian-surface 2021-01-24 21:45:01 -08:00
Alexander Rose
f79f1507f7 dispose of volume & repr associated textures 2021-01-24 21:44:02 -08:00
Alexander Rose
61ab205a5d recreate visuals based on param changes
- impostor/mesh (spacefill, ball+stick, ellipsoids)
- gpu/cpu mc (isosurface, gaussian surface)
2021-01-23 16:57:21 -08:00
Alexander Rose
2c65260a4f Merge remote-tracking branch 'origin/param-normalization' into cylinders 2021-01-23 16:39:23 -08:00
Alexander Rose
0597a1ef24 Merge branch 'master' into cylinders 2021-01-23 16:35:18 -08:00
Alexander Rose
8d6557e51c moved label-options out of palette-params 2021-01-23 16:25:32 -08:00
Alexander Rose
5cff0dff3d wip, gpu mc
- int float div
- clamp 2d texture access to 3d grid bounds
2021-01-23 15:46:29 -08:00
Alexander Rose
93206e76d7 fix DVR with orthographic projection 2021-01-23 13:36:28 -08:00
dsehnal
40933a8539 1.2.12 2021-01-20 14:18:55 +01:00
dsehnal
989800783b dihedral visual update 2021-01-20 14:13:57 +01:00
dsehnal
d83b0d2c4d better typing for PD.MultiSelect & StructureMeasurementManager visualParams support 2021-01-20 10:30:59 +01:00
dsehnal
5e5d5a63dc mol-plugin: subscribe to events in initViewer
+ handle plugin resize in main render loop
2021-01-19 15:09:57 +01:00
Alexander Rose
b1755604e2 fix webgl context loss handling 2021-01-18 19:48:55 -08:00
Alexander Rose
e58da9b574 add Canvas3DContext
- can be used to create multiple Canvas3D objects
2021-01-18 11:30:42 -08:00
dsehnal
f5d6498601 ParamDefinition.normalizeParams tweaks 2021-01-17 11:50:15 +01:00
dsehnal
07f351888f add doNotForceWebGLContextLoss option 2021-01-17 11:15:15 +01:00
Alexander Rose
4588fdd5d5 wip, gpu mc webgl1 tweak 2021-01-16 17:24:26 -08:00
Alexander Rose
c3b32baf6a wip, gpu mc & vol isosurface 2021-01-16 15:18:41 -08:00
Alexander Rose
b8d60cea9b Canvas3d.fromCanvas attribs
- simplified antialias handling
- expose preserveDrawingBuffer
2021-01-16 13:20:32 -08:00
Alexander Rose
25b8956712 moved rgba/float conversion to glsl chunks 2021-01-16 11:40:54 -08:00
Alexander Rose
7015309db6 added more interesting pdb entries 2021-01-16 11:37:48 -08:00
Alexander Rose
aad861db37 use var in webpack version template 2021-01-16 11:37:20 -08:00
Alexander Rose
ae7811705d fix volume isosurface picking 2021-01-16 11:35:55 -08:00
dsehnal
7e26dac50b 1.2.11 2021-01-15 18:40:05 +01:00
dsehnal
75f43d038c PluginConfig.General.ForceWboitAntialiasing 2021-01-15 18:37:29 +01:00
dsehnal
b9ba940510 dihedral visual updates
- fix "extenders"
- add "arms" visual
2021-01-15 15:10:33 +01:00
dsehnal
35603baaaa 1.2.10 2021-01-15 13:28:01 +01:00
dsehnal
19dc32c491 Canvas3D.dispose lose webgl context 2021-01-15 13:21:34 +01:00
dsehnal
95997e6a61 PickScale plugin config 2021-01-15 12:54:02 +01:00
dsehnal
03e19a2ad7 css tweaks 2021-01-15 12:50:30 +01:00
dsehnal
765b133369 Merge branch 'master' into cylinders 2021-01-13 16:50:11 +01:00
dsehnal
703e729514 PD.normalizeParams options 2021-01-13 16:49:33 +01:00
dsehnal
b0216c4ce6 Merge branch 'master' into cylinders 2021-01-13 16:10:39 +01:00
dsehnal
6796fc1cd4 use PD.normalizeParams in canvas3d.setProps 2021-01-13 16:09:48 +01:00
dsehnal
87c504f9a8 mol-state: use PD.normalizeParams first time a cell is evaluated 2021-01-13 15:59:50 +01:00
dsehnal
2e770cb733 ParamDefinition.normalizeParams 2021-01-13 15:44:05 +01:00
David Sehnal
9f440f68e0 Merge pull request #118 from JonStargaryen/modelserverfixes
ModelServer: filename parameter
2021-01-13 12:57:01 +01:00
JonStargaryen
40028b27ba cleanup 2021-01-12 10:47:18 -08:00
JonStargaryen
4676ad8738 gracefully handle empty param category 2021-01-12 10:44:47 -08:00
JonStargaryen
e1c7833826 filename param 2021-01-12 10:25:55 -08:00
Alexander Rose
dd1bca0fee Merge branch 'master' into cylinders 2021-01-10 00:45:18 -08:00
Alexander Rose
c38ab2c638 use existing gl context for capability testing
- fixes unit-test
2021-01-10 00:38:16 -08:00
Alexander Rose
459c5aa5a7 wip, gpu mc
- reduced texture sizes
- structure gaussian surface texture-mesh
2021-01-10 00:17:41 -08:00
Alexander Rose
b8bf07d393 byto-count info for webgl resources 2021-01-10 00:09:20 -08:00
Alexander Rose
ea87ac2094 add and use gpu half-float support
- add texture_half_float, texture_half_float_linear, color_buffer_half_float
- use in multie-sample, gaussian-density
2021-01-10 00:07:12 -08:00
Alexander Rose
e1b830a59d improved atomicDetail preset 2021-01-09 14:23:19 -08:00
Alexander Rose
41e1ac76c0 improve peptide entity-subtype derivation 2021-01-09 12:51:34 -08:00
Alexander Rose
98b118fd1e parse pdb conect records
- heuristic to test if list of bonds is exhaustive to skip auto-bonding
2021-01-09 12:49:44 -08:00
Alexander Rose
5f691913e4 fix webgl1 screendoor transparency
- webgl1 only allows const array access
2021-01-09 11:16:32 -08:00
Alexander Rose
26e2516097 fix traceOnly param getting ignored 2021-01-09 11:15:13 -08:00
dsehnal
3d2e4115ed 1.2.9 2021-01-08 15:17:42 +01:00
dsehnal
dbce1ccb3d alpha-orbitals: ability to clamp volume values 2021-01-08 15:16:08 +01:00
David Sehnal
03aa2be978 Merge pull request #116 from JonStargaryen/modelserverfixes
ModelServer: Add option to download text files
2021-01-07 17:33:38 +01:00
JonStargaryen
8dfc52e1ab cleanup 2021-01-07 14:49:32 +01:00
JonStargaryen
6058179f10 cleanup 2021-01-07 14:36:11 +01:00
JonStargaryen
ea9e25b03c ResultWriterParams 2021-01-07 14:30:01 +01:00
JonStargaryen
d60c3ddce3 handle non-string params faithfully 2021-01-07 14:21:32 +01:00
JonStargaryen
724e79bddf version/changelog 2021-01-07 12:53:44 +01:00
JonStargaryen
2de61215c4 better description 2021-01-07 12:35:16 +01:00
JonStargaryen
e783d9a9f1 Merge remote-tracking branch 'upstream/master' into modelserverfixes 2021-01-07 12:31:26 +01:00
JonStargaryen
e9e971d4f3 lock 2021-01-07 12:31:20 +01:00
JonStargaryen
96dea14cb1 cleanup 2021-01-07 12:26:50 +01:00
JonStargaryen
04fc157340 ModelServer: Save As param 2021-01-07 11:53:24 +01:00
dsehnal
cfc24fa99e SequenceView: fix polymers & everything modes 2021-01-07 11:22:57 +01:00
dsehnal
19c1088209 1.2.8 2021-01-06 15:29:29 +01:00
dsehnal
ee6c2e0841 canvas3d: add commited event 2021-01-06 15:22:54 +01:00
Alexander Rose
20ee659b00 wip, gaussian surface
- fix webgl1 gaussian volume broken
- fix 2d volume slice missing first row
- use scissor test to avoid useless calculations
- reduce radius for which gaussians are calculated
-
2021-01-03 15:15:03 -08:00
Alexander Rose
b6514a4a50 fix structure bond count calculation 2021-01-03 14:57:18 -08:00
Alexander Rose
07b8bdb951 noClip option for renderables
- exclude handle and axes helper from clip objects
2021-01-03 14:56:40 -08:00
Alexander Rose
afd18cabd4 tweaked renderer params to enable AO by default 2021-01-03 14:42:35 -08:00
Alexander Rose
1117ce05d5 improved picking alpha and fog shader code 2021-01-03 14:37:49 -08:00
Alexander Rose
fc15e952bf fix smaa viewport handling 2021-01-03 14:34:34 -08:00
dsehnal
249e5a3e0b SequenceView improvements
- add ability to show all polymers/chains at once
- do not redraw when structure list doesn't change (saves 3 re-renders for a typical structure load)
2020-12-29 16:29:16 +01:00
Alexander Rose
4bfe3f6bde postprocesing tweaks
- better distingush objects close to far plane from background
- draw outlines last to be cleaner
- allow larger AO radius
2020-12-21 22:38:30 -08:00
Alexander Rose
75b7e0b4d9 support to 'invert' clip object test
- e.g. to cut away everything but a sphere
2020-12-20 20:06:52 -08:00
Alexander Rose
ee4ce2fd7a simplify copy shader 2020-12-20 14:35:17 -08:00
Alexander Rose
db0aa12e75 Merge branch 'master' into cylinders 2020-12-20 13:45:50 -08:00
Alexander Rose
6d2578d3d0 repr/geo param update fixes
- texture-mesh geo
- text visual
2020-12-20 13:40:33 -08:00
Alexander Rose
99d61f48b4 Merge branch 'master' into cylinders 2020-12-20 13:00:18 -08:00
Alexander Rose
146022dc12 wip, gaussian surface & mc
- fix iso-level
- reuse gpu resources for mc (patched many memory leaks)
2020-12-20 12:55:54 -08:00
Alexander Rose
92730cad01 Merge branch 'master' into cylinders 2020-12-19 21:33:24 -08:00
Alexander Rose
d6b68b06da Merge branch 'cylinders' of https://github.com/molstar/molstar into cylinders 2020-12-19 21:31:16 -08:00
Alexander Rose
b174fbf0c6 postprocessing tweaks and fixes/improvements
- AO defaults: darker, larger radius
- handle transparent bg for outlines & AO
- handle fog for AO
- fix fog for outlines
- fragmentDepth for fog (instead of camera distance)
- webgl1 compat
2020-12-19 21:26:06 -08:00
Alexander Rose
fde1557955 Merge branch 'postprocessing' into cylinders
- added AntialiasingPass
2020-12-19 17:38:57 -08:00
AronKovacs
24a0753881 fix fog for outlines 2020-12-19 21:59:56 +01:00
dsehnal
5664e1d8be 1.2.7 2020-12-19 11:53:09 +01:00
dsehnal
4881a41256 set default camera radius/max = 0 2020-12-19 11:46:00 +01:00
dsehnal
235e41ee03 PluginConfig EnableWboit => true 2020-12-19 11:26:13 +01:00
AronKovacs
94d293a4d3 renaming, better defaults, ao bias, better outline thresholding, whitespace changes 2020-12-18 16:10:37 +01:00
AronKovacs
40f1ca207f replaced placeholder value with the correct uniform 2020-12-16 17:58:00 +01:00
AronKovacs
926fb38c1e added contributors 2020-12-16 17:43:20 +01:00
AronKovacs
5a14fcabc5 small ssao changes, e.g. better vec2 noise 2020-12-16 17:38:38 +01:00
AronKovacs
560e40773f added renderBlended postprocessing 2020-12-16 17:21:26 +01:00
AronKovacs
6561732f57 Merge remote-tracking branch 'upstream/master' into postprocessing 2020-12-15 13:30:41 +01:00
AronKovacs
b45cf206fd postprocessing init 2020-12-15 13:27:11 +01:00
Alexander Rose
70e07be64d anvil tweaks
- remove unused/broken bilayer-spheres visual
- ensure anvil prop is calculated
2020-12-12 18:23:07 -08:00
Alexander Rose
f3013f0e46 smaa param tweaks 2020-12-12 17:31:27 -08:00
Alexander Rose
2e7041bd78 remove debug statement 2020-12-12 17:29:19 -08:00
Alexander Rose
5d0447c9bb enable wboit by default 2020-12-12 16:20:59 -08:00
Alexander Rose
9eba0b91a8 add smaa antialiasing option (new default) 2020-12-12 16:13:53 -08:00
Alexander Rose
58bc6722a9 moved fxaa to separate pass 2020-12-12 15:42:56 -08:00
Alexander Rose
1acfed3233 naming and doc tweaks 2020-12-12 15:39:45 -08:00
dsehnal
8147b3aa34 1.2.6 2020-12-10 10:46:26 +01:00
dsehnal
b21552ff36 fix wboit rendering when updating alpha 2020-12-10 10:44:35 +01:00
dsehnal
c683cbe962 1.2.5 2020-12-09 15:06:17 +01:00
dsehnal
bd270e4428 fix pdbx_PDB_ins_code "prefixed" names in CIF exporter 2020-12-09 15:03:41 +01:00
dsehnal
23d942d8a5 updated packages 2020-12-09 14:55:25 +01:00
Alexander Rose
cbcd6b99d2 Merge pull request #107 from molstar/remove-3dg
remove 3dg in favor of g3d (#93)
2020-12-05 20:44:16 -08:00
Alexander Rose
ee5c098a9f remove 3dg in favor of g3d (#93) 2020-12-05 20:38:50 -08:00
Alexander Rose
070a15d679 antialiasing related tweaks (combat blurriness)
- increase default line and point size
- reduce subpixel quality in fxaa
2020-12-05 16:02:08 -08:00
Alexander Rose
befa5174f8 cylinder impostors for bonds
- inter/intra bonds
- ball & stick, ellipsoids
- new link visual helper
2020-12-05 15:49:59 -08:00
Alexander Rose
d6c4366f40 link visual helper improvements
- more configurable dashes
- better cap handling
2020-12-05 15:28:22 -08:00
Alexander Rose
181cfefa63 use bond location for repr bond iterator
- fix themes to handle Bond.Location (some did not)
2020-12-05 15:22:04 -08:00
Alexander Rose
0e7c885961 fix typo, remove unused code 2020-12-05 15:17:14 -08:00
Alexander Rose
d58e90d93f add cylinders geometry and shader 2020-12-05 15:15:29 -08:00
David Sehnal
cd872b47e6 1.2.4 2020-12-03 15:30:30 +01:00
David Sehnal
2683c5b318 Merge pull request #105 from molstar/gpu-grid
GPU grid 3d computation wrapper
2020-12-03 15:24:43 +01:00
David Sehnal
c71f60a164 ParamDefinition.DataRef 2020-12-03 15:21:42 +01:00
David Sehnal
881cbc1947 tweaks 2020-12-03 13:54:51 +01:00
David Sehnal
f3e7febbd1 Merge branch 'master' of https://github.com/molstar/molstar into gpu-grid 2020-12-03 06:33:19 +01:00
David Sehnal
e68ad13031 createGrid3dComputeRenderable yieldPeriod param 2020-12-02 12:29:01 +01:00
Alexander Rose
7fbbe1e63a representation state and hightlight fixes
- recreate state when repr changes
- take repr into account for non-hover hightlights (eg from state tree)
2020-12-01 17:48:40 -08:00
Alexander Rose
a5ca72af3c postprocessing tweaks and fixes
- fix missing enable scissor state
- better antialiasing defaults
- always allow fxaa
2020-12-01 17:46:51 -08:00
David Sehnal
1ce6641eb3 grid3d-compute util code 2020-12-01 20:58:27 +01:00
David Sehnal
5dc413ab8c wip grid3d renderable 2020-12-01 19:33:05 +01:00
David Sehnal
50b615e86c 1.2.3 2020-11-28 14:50:15 +01:00
David Sehnal
5b4c6743e7 GlobalModelTransformInfo
- support in volume streaming
- export in ModelServer if transform param is present
2020-11-28 14:46:58 +01:00
668 changed files with 37656 additions and 12489 deletions

View File

@@ -31,7 +31,14 @@
"no-unsafe-finally": "warn",
"no-var": "error",
"spaced-comment": "error",
"semi": "warn"
"semi": "warn",
"no-restricted-syntax": [
"error",
{
"selector": "ExportDefaultDeclaration",
"message": "Default exports are not allowed"
}
]
},
"overrides": [
{

2
.gitignore vendored
View File

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

View File

@@ -1 +1 @@
tsconfig.commonjs.buildinfo
tsconfig.commonjs.tsbuildinfo

118
CHANGELOG.md Normal file
View File

@@ -0,0 +1,118 @@
# Change Log
All notable changes to this project will be documented in this file, following the suggestions of [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic Versioning](http://semver.org/) for its most widely used - and defacto - public interfaces.
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
## [v2.2.2] - 2021-08-11
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
- Fix ``mol-script`` query compiler const expression recognition.
## [v2.2.1] - 2021-08-02
- Add surrounding atoms (5 Angstrom) structure selection query
- [Breaking] Add maxDistance prop to ``IndexPairBonds``
- Fix coordinateSystem not handled in ``Structure.asParent``
- Add ``dynamicBonds`` to ``Structure`` props (force re-calc on model change)
- Expose as optional param in root structure transform helper
- Add overpaint support to geometry exporters
- ``InputObserver`` improvements
- normalize wheel speed across browsers/platforms
- support Safari gestures (used by ``TrackballControls``)
- ``PinchInput.fractionDelta`` and use it in ``TrackballControls``
## [v2.2.0] - 2021-07-31
- Add ``tubularHelices`` parameter to Cartoon representation
- Add ``SdfFormat`` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
- Fix mononucleotides detected as polymer components (#229)
- Set default outline scale back to 1
- Improved DCD reader cell angle handling (interpret near 0 angles as 90 deg)
- Handle more residue/atom names commonly used in force-fields
- Add USDZ support to ``geo-export`` extension.
- Fix ``includeParent`` support for multi-instance bond visuals.
- Add ``operator`` Loci granularity, selecting everything with the same operator name.
- Prefer ``_label_seq_id`` fields in secondary structure assignment.
- Support new EMDB API (https://www.ebi.ac.uk/emdb/api/entry/map/[EMBD-ID]) for EM volume contour levels.
- ``Canvas3D`` tweaks:
- Update ``forceDraw`` logic.
- Ensure the scene is re-rendered when viewport size changes.
- Support ``noDraw`` mode in ``PluginAnimationLoop``.
## [v2.1.0] - 2021-07-05
- Add parameter for to display aromatic bonds as dashes next to solid cylinder/line.
- Add backbone representation
- Fix outline in orthographic mode and set default scale to 2.
## [v2.0.7] - 2021-06-23
- Add ability to specify ``volumeIndex`` in ``Viewer.loadVolumeFromUrl`` to better support Volume Server inputs.
- Support in-place reordering for trajectory ``Frame.x/y/z`` arrays for better memory efficiency.
- Fixed text CIF encoder edge cases (most notably single whitespace not being escaped).
## [v2.0.6] - 2021-06-01
- Add glTF (GLB) and STL support to ``geo-export`` extension.
- Protein crosslink improvements
- Change O-S bond distance to allow for NOS bridges (doi:10.1038/s41586-021-03513-3)
- Added NOS-bridges query & improved disulfide-bridges query
- Fix #178: ``IndexPairBonds`` for non-single residue structures (bug due to atom reordering).
- Add volumetric color smoothing for MolecularSurface and GaussianSurface representations (#173)
- Fix nested 3d grid lookup that caused results being overwritten in non-covalent interactions computation.
- Basic implementation of ``BestDatabaseSequenceMapping`` (parse from CIF, color theme, superposition).
- Add atom id ranges support to Selection UI.
## [v2.0.5] - 2021-04-26
- Ability to pass ``Canvas3DContext`` to ``PluginContext.fromCanvas``.
- Relative frame support for ``Canvas3D`` viewport.
- Fix bug in screenshot copy UI.
- Add ability to select residues from a list of identifiers to the Selection UI.
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
- Add ``MeshBuilder.addMesh``.
- Add ``Torus`` primitive.
- Lazy volume loading support.
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
- Add ``TextureMesh`` support to ``geo-export`` extension.
## [v2.0.4] - 2021-04-20
- [WIP] Mesh export extension
- ``Structure.eachAtomicHierarchyElement`` (#161)
- Fixed reading multi-line values in SDF format
- Fixed Measurements UI labels (#166)
## [v2.0.3] - 2021-04-09
### Added
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
### Changed
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- ``Canvas3D.getRenderObjects``.
- [WIP] Animate state interpolating, including model trajectories
### Changed
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
### Fixed
- VolumeFromDensityServerCif transform label
## [v2.0.1] - 2021-03-23
### Fixed
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
## [v2.0.0] - 2021-03-23
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

View File

@@ -5,15 +5,15 @@
# Mol*
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
This particular project is the implementation of this technology (still under development).
When using Mol*, please cite:
*If you are looking for the "MOLeculAR structure annoTator", that package is now available on NPM as [MolArt](https://www.npmjs.com/package/molart).*
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: [Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures](https://doi.org/10.1093/nar/gkab314), *Nucleic Acids Research*, 2021; https://doi.org/10.1093/nar/gkab314.
## Project Overview
## Project Structure Overview
The core of Mol* currently consists of these modules (see under `src/`):
The core of Mol* consists of these modules (see under `src/`):
- `mol-task` Computation abstraction with progress tracking and cancellation support.
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
@@ -29,7 +29,6 @@ The core of Mol* currently consists of these modules (see under `src/`):
- `mol-gl` A wrapper around WebGL.
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
- `mol-state` State representation tree with state saving and automatic updates.
- `mol-app` Components for building UIs.
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
- `mol-plugin-state` State transformations, builders, and managers.
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
@@ -41,7 +40,7 @@ Moreover, the project contains the implementation of `servers`, including
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
- `servers/plugin-state` A basic server to store Mol* Plugin states.
The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
The project also contains performance tests (`perf-tests`), `examples`, and `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
## Previous Work
This project builds on experience from previous solutions:
@@ -169,12 +168,9 @@ To get syntax highlighting for shader and graphql files add the following to Vis
## Contributing
Just open an issue or make a pull request. All contributions are welcome.
## Roadmap
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
## Funding
Funding sources include but are not limited to:
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
* [PDBe, EMBL-EBI](https://pdbe.org)
* [CEITEC](https://www.ceitec.eu/)
* [EntosAI](https://www.entos.ai) (``alpha-orbitals`` extension)
* [EntosAI](https://www.entos.ai)

View File

@@ -24,4 +24,12 @@
* Close backbone atoms but not linked (e.g. 4HIV)
* Non-standard residues
* Protein (1BRR, 5Z6Y)
* DNA (5D3G)
* DNA (5D3G)
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
* Long linear sugar chain (4HG6)
* Anisotropic B-factors/Ellipsoids (1EJG)
* NOS bridges (LYS-CSO in 7B0L, 6ZWJ, 6ZWH)
Assembly symmetries
* 5M30 (Assembly 1, C3 local and pseudo)
* 1RB8 (Assembly 1, I global)

26710
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "1.2.2",
"version": "2.2.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -14,6 +14,7 @@
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
@@ -26,9 +27,9 @@
"watch-tsc": "tsc --watch --incremental",
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --display minimal",
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
"watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
"watch-webpack": "webpack -w --mode development --stats minimal",
"watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js",
"watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js",
"serve": "http-server -p 1338 -g",
"model-server": "node lib/commonjs/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
@@ -80,70 +81,74 @@
"Alexander Rose <alexander.rose@weirdbyte.de>",
"David Sehnal <david.sehnal@gmail.com>",
"Sebastian Bittrich <sebastian.bittrich@rcsb.org>",
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
"Ludovic Autin <autin@scripps.edu>",
"Michal Malý <michal.maly@ibt.cas.cz>",
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^1.17.7",
"@graphql-codegen/cli": "^1.17.8",
"@graphql-codegen/time": "^1.17.10",
"@graphql-codegen/typescript": "^1.17.9",
"@graphql-codegen/typescript-graphql-files-modules": "^1.17.8",
"@graphql-codegen/typescript-graphql-request": "^1.17.7",
"@graphql-codegen/typescript-operations": "^1.17.8",
"@types/cors": "^2.8.7",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"@graphql-codegen/add": "^2.0.2",
"@graphql-codegen/cli": "^1.19.4",
"@graphql-codegen/time": "^2.0.2",
"@graphql-codegen/typescript": "^1.19.0",
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
"@graphql-codegen/typescript-operations": "^1.17.12",
"@types/cors": "^2.8.8",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"benchmark": "^2.1.4",
"concurrently": "^5.3.0",
"cpx2": "^2.0.0",
"css-loader": "^3.6.0",
"eslint": "^7.8.1",
"cpx2": "^3.0.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.1.0",
"file-loader": "^6.2.0",
"fs-extra": "^9.0.1",
"graphql": "^15.3.0",
"graphql": "^15.4.0",
"http-server": "^0.12.3",
"jest": "^26.4.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.14.1",
"raw-loader": "^4.0.1",
"sass-loader": "^8.0.2",
"simple-git": "^2.20.1",
"style-loader": "^1.2.1",
"ts-jest": "^26.3.0",
"typescript": "^4.0.2",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.3.2",
"node-sass": "^6.0.0",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass-loader": "^11.1.1",
"simple-git": "^2.25.0",
"stream-browserify": "^3.0.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.2.4",
"webpack": "^5.37.1",
"webpack-cli": "^4.7.0",
"webpack-version-file-plugin": "^0.4.0"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^1.0.33",
"@types/benchmark": "^2.1.0",
"@types/compression": "1.7.0",
"@types/express": "^4.17.8",
"@types/jest": "^25.2.3",
"@types/node": "^14.10.1",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.18",
"@types/node": "^14.14.11",
"@types/node-fetch": "^2.5.7",
"@types/react": "^16.9.49",
"@types/react-dom": "^16.9.8",
"@types/swagger-ui-dist": "3.0.5",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/swagger-ui-dist": "3.30.0",
"argparse": "^1.0.10",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^7.0.9",
"immer": "^8.0.1",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rxjs": "^6.6.3",
"swagger-ui-dist": "^3.33.0",
"tslib": "^2.0.1",
"node-fetch": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rxjs": "^6.6.6",
"swagger-ui-dist": "^3.37.2",
"tslib": "^2.1.0",
"util.promisify": "^1.0.1",
"xhr2": "^0.2.0"
}

View File

@@ -5,30 +5,32 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import '../../mol-util/polyfill';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import './index.html';
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginConfig } from '../../mol-plugin/config';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { Structure } from '../../mol-model/structure';
import { PluginStateTransform, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Task } from '../../mol-task';
import { StateObject } from '../../mol-state';
import { ViewportComponent, StructurePreset, ShowButtons } from './viewport';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginBehaviors } from '../../mol-plugin/behavior';
import { ColorNames } from '../../mol-util/color/names';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginSpec } from '../../mol-plugin/spec';
import { StateObject } from '../../mol-state';
import { Task } from '../../mol-task';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import './index.html';
import { ShowButtons, StructurePreset, ViewportComponent } from './viewport';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
export { Viewer as DockingViewer };
const DefaultViewerOptions = {
extensions: ObjectKeys({}),
@@ -52,7 +54,7 @@ const DefaultViewerOptions = {
};
class Viewer {
plugin: PluginContext
plugin: PluginUIContext
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{
@@ -69,9 +71,10 @@ class Viewer {
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions],
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci, { mark: false }),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
@@ -81,23 +84,23 @@ class Viewer {
PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
],
animations: [...DefaultPluginSpec.animations || []],
customParamEditors: DefaultPluginSpec.customParamEditors,
animations: defaultSpec.animations,
customParamEditors: defaultSpec.customParamEditors,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
},
},
components: {
...defaultSpec.components,
controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...DefaultPluginSpec.components,
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
viewport: {
view: ViewportComponent
@@ -208,4 +211,3 @@ const MergeStructures = PluginStateTransform.BuiltIn({
});
(window as any).DockingViewer = Viewer;
export { Viewer as DockingViewer };

View File

@@ -4,24 +4,23 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { PluginUIComponent } from '../../mol-plugin-ui/base';
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
import { LociLabels } from '../../mol-plugin-ui/controls';
import { Toasts } from '../../mol-plugin-ui/toast';
import { Button } from '../../mol-plugin-ui/controls/common';
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../mol-state';
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions';
import { InteractionTypeColorThemeProvider } from '../../mol-model-props/computed/themes/interaction-type';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { presetStaticComponent, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { Color } from '../../mol-util/color';
import { PluginUIComponent } from '../../mol-plugin-ui/base';
import { LociLabels } from '../../mol-plugin-ui/controls';
import { Button } from '../../mol-plugin-ui/controls/common';
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
import { Toasts } from '../../mol-plugin-ui/toast';
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateObjectRef } from '../../mol-state';
import { Color } from '../../mol-util/color';
function shinyStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
@@ -46,13 +45,14 @@ function occlusionStyle(plugin: PluginContext) {
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'on', params: {
kernelSize: 8,
bias: 0.8,
radius: 64
samples: 64,
radius: 8,
bias: 1.0,
blurKernelSize: 13
} },
outline: { name: 'on', params: {
scale: 1.0,
threshold: 0.8
threshold: 0.33
} }
}
} });

View File

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

View File

@@ -48,18 +48,14 @@
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
var disableAntialiasing = getParam('disable-antialiasing', '[^&]+').trim() === '1';
var pixelScale = parseFloat(getParam('pixel-scale', '[^&]+').trim() || '1');
var enableWboit = getParam('enable-wboit', '[^&]+').trim() === '1';
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
var viewer = new molstar.Viewer('app', {
disableAntialiasing: disableAntialiasing,
pixelScale: pixelScale,
enableWboit: enableWboit,
layoutShowControls: !hideControls,
viewportShowExpand: false,
collapseLeftPanel: collapseLeftPanel,
pdbProvider: pdbProvider || 'pdbe',
emdbProvider: emdbProvider || 'pdbe',
});

View File

@@ -5,42 +5,44 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import '../../mol-util/polyfill';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import './index.html';
import './embedded.html';
import './favicon.ico';
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginSpec } from '../../mol-plugin/spec';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { PluginConfig } from '../../mol-plugin/config';
import { CellPack } from '../../extensions/cellpack';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { Asset } from '../../mol-util/assets';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { PluginState } from '../../mol-plugin/state';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Mp4Export } from '../../extensions/mp4-export';
import { GeometryExport } from '../../extensions/geo-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { Color } from '../../mol-util/color';
import { StateObjectSelector } from '../../mol-state';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { Mp4Export } from '../../extensions/mp4-export';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import './embedded.html';
import './favicon.ico';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
@@ -54,7 +56,8 @@ const Extensions = {
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport)
};
const DefaultViewerOptions = {
@@ -67,9 +70,10 @@ const DefaultViewerOptions = {
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
disableAntialiasing: false,
pixelScale: 1,
enableWboit: false,
enableWboit: true,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -85,35 +89,42 @@ const DefaultViewerOptions = {
type ViewerOptions = typeof DefaultViewerOptions;
export class Viewer {
plugin: PluginContext
plugin: PluginUIContext
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions],
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...DefaultPluginSpec.behaviors,
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
],
animations: [...DefaultPluginSpec.animations || []],
customParamEditors: DefaultPluginSpec.customParamEditors,
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: 'full',
top: 'full',
}
},
},
components: {
...defaultSpec.components,
controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...DefaultPluginSpec.components,
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
@@ -232,28 +243,81 @@ export class Viewer {
}));
}
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
/**
* @example Load X-ray density from volume server
viewer.loadVolumeFromUrl({
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1.5,
color: 0x3362B2
}, {
type: 'relative',
value: 3,
color: 0x33BB33,
volumeIndex: 1
}, {
type: 'relative',
value: -3,
color: 0xBB3333,
volumeIndex: 1
}], {
entryId: ['2FO-FC', 'FO-FC'],
isLazy: true
});
* *********************
* @example Load EM density from volume server
viewer.loadVolumeFromUrl({
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1,
color: 0x3377aa
}], {
entryId: 'EMD-30210',
isLazy: true
});
*/
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
});
return update.commit();
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build().to(volume);
const repr = plugin.build();
for (const iso of isovalues) {
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
repr
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
@@ -273,5 +337,6 @@ export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number
alpha?: number,
volumeIndex?: number
}

View File

@@ -70,7 +70,7 @@ function classify(name: string, field: CifField): CifWriter.Field {
}
}
export default function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
export function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
const encodingProvider: BinaryEncodingProvider = hints
? CifWriter.createEncodingProviderFromJsonConfig(hints)

View File

@@ -10,7 +10,7 @@ import * as argparse from 'argparse';
import * as util from 'util';
import * as fs from 'fs';
import * as zlib from 'zlib';
import convert from './converter';
import { convert } from './converter';
require('util.promisify').shim();

View File

@@ -12,7 +12,7 @@ import fetch from 'node-fetch';
import { parseCsv } from '../../mol-io/reader/csv/parser';
import { CifFrame, CifBlock } from '../../mol-io/reader/cif';
import parseText from '../../mol-io/reader/cif/text/parser';
import { parseCifText } from '../../mol-io/reader/cif/text/parser';
import { generateSchema } from './util/cif-dic';
import { generate } from './util/generate';
import { Filter, Database } from './util/schema';
@@ -28,19 +28,19 @@ function getDicNamespace(block: CifBlock) {
async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
await ensureMmcifDicAvailable();
const mmcifDic = await parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
const mmcifDic = await parseCifText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
if (mmcifDic.isError) throw mmcifDic;
await ensureIhmDicAvailable();
const ihmDic = await parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
const ihmDic = await parseCifText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
if (ihmDic.isError) throw ihmDic;
await ensureCarbBranchDicAvailable();
const carbBranchDic = await parseText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
const carbBranchDic = await parseCifText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
if (carbBranchDic.isError) throw carbBranchDic;
await ensureCarbCompDicAvailable();
const carbCompDic = await parseText(fs.readFileSync(CARB_COMP_DIC_PATH, 'utf8')).run();
const carbCompDic = await parseCifText(fs.readFileSync(CARB_COMP_DIC_PATH, 'utf8')).run();
if (carbCompDic.isError) throw carbCompDic;
const mmcifDicVersion = getDicVersion(mmcifDic.result.blocks[0]);
@@ -56,7 +56,7 @@ async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, type
async function runGenerateSchemaCifCore(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
await ensureCifCoreDicAvailable();
const cifCoreDic = await parseText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
const cifCoreDic = await parseCifText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
if (cifCoreDic.isError) throw cifCoreDic;
const cifCoreDicVersion = getDicVersion(cifCoreDic.result.blocks[0]);
@@ -80,7 +80,7 @@ async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<
if (!file) continue;
if (imports.has(file)) continue;
const dic = await parseText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
const dic = await parseCifText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
if (dic.isError) throw dic;
imports.set(file, [...dic.result.blocks[0].saveFrames]);
@@ -92,7 +92,7 @@ async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<
}
async function runGenerateSchemaDic(name: string, dicPath: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
const dic = await parseText(fs.readFileSync(dicPath, 'utf8')).run();
const dic = await parseCifText(fs.readFileSync(dicPath, 'utf8')).run();
if (dic.isError) throw dic;
const dicVersion = getDicVersion(dic.result.blocks[0]);

View File

@@ -18,7 +18,6 @@ _.StateTransforms.Data.Download.id;
// Empty plugin context
const ctx = new PluginContext({
actions: [],
behaviors: []
});

View File

@@ -26,7 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'file': return `JavaScript File Handle`;
case 'file-list': return `JavaScript FileList Handle`;
case 'select': return `One of ${oToS(param.options)}`;
case 'value-ref': return `Reference to a state object.`;
case 'value-ref': return `Reference to a runtime defined value.`;
case 'data-ref': return `Reference to a computed data value.`;
case 'text': return 'String';
case 'interval': return `Interval [min, max]`;
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';

View File

@@ -22,7 +22,23 @@
left: 8px;
top: 8px;
width: 300px;
}
}
#sponsor {
position: absolute;
left: 8px;
bottom: 8px;
font-family: "Helvetica Neue", "Segoe UI", Helvetica, "Source Sans Pro", Arial, sans-serif;
font-size: 12px;
text-align: center;
}
#sponsor svg {
fill: #128EA4;
width: 100px;
}
#sponsor a {
text-decoration: none;
color: #128EA4;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./index.js"></script>
@@ -30,6 +46,14 @@
<body>
<div id="app"></div>
<div id='controls'></div>
<div id='sponsor'>
<a href='https://www.entos.ai/envision' target="_blank" rel="noopener">
<svg class="makeStyles-root-46" viewBox="0 0 190 36" xmlns="http://www.w3.org/2000/svg"><path d="M32.2591 28.6707C32.2591 32.3914 29.2421 35.407 25.5214 35.407C22.0752 35.407 19.2338 32.8206 18.8325 29.4831V29.4775C18.8143 29.3312 18.8018 29.1835 18.7934 29.0344C18.7934 29.0316 18.7921 29.0274 18.7921 29.0246V29.0177C18.7865 28.902 18.7837 28.7864 18.7837 28.6707C18.7837 26.2557 20.0532 24.1389 21.9609 22.9503C21.9623 22.9489 21.9651 22.9489 21.9665 22.9475C22.0933 22.8666 22.2243 22.7914 22.3581 22.7203C22.3581 22.7203 22.3595 22.7203 22.3595 22.7189C23.3029 22.2173 24.3787 21.933 25.5214 21.933C29.2421 21.933 32.2591 24.9486 32.2591 28.6707Z"></path><path d="M25.5214 14.0692C29.2421 14.0692 32.2591 11.0522 32.2591 7.33146C32.2591 3.61074 29.2421 0.59375 25.5214 0.59375C22.0529 0.59375 19.1962 3.21637 18.8255 6.58592C18.8185 6.67092 18.8116 6.75454 18.8018 6.83815C18.7893 7.00119 18.7837 7.16563 18.7837 7.33146C18.7837 9.73669 20.0434 11.8465 21.94 13.038C22.0891 13.116 22.2355 13.201 22.3776 13.2916C22.3783 13.2923 22.379 13.2926 22.3797 13.293C22.3804 13.2933 22.3811 13.2937 22.3818 13.2944C23.3196 13.7891 24.3871 14.0692 25.5214 14.0692Z"></path><path d="M19.3645 12.4113C20.2926 12.4113 21.1694 12.638 21.94 13.038C20.0434 11.8465 18.7837 9.73669 18.7837 7.33146C18.7837 7.16563 18.7893 7.00119 18.8018 6.83815C18.4688 9.76455 16.1385 12.0866 13.2065 12.4044C13.8545 13.1193 14.3785 13.9484 14.745 14.857C15.7497 13.3798 17.4443 12.4113 19.3645 12.4113Z"></path><path d="M14.7312 21.1249V21.1236C14.1279 20.2331 13.7767 19.1587 13.7767 18.0007V17.9728C13.7767 15.3084 12.2285 13.0035 9.9835 11.911C9.98141 11.9103 9.97967 11.9096 9.97793 11.9089C9.97619 11.9082 9.97444 11.9075 9.97235 11.9068C9.96817 11.904 9.96538 11.9026 9.9612 11.9012C9.95981 11.9012 9.95981 11.8998 9.95981 11.8998C9.9417 11.8915 9.92394 11.8831 9.90618 11.8747C9.8884 11.8664 9.87063 11.858 9.85251 11.8497C9.82046 11.8343 9.78701 11.819 9.75357 11.8051L9.74521 11.8009C8.91745 11.4372 8.0019 11.2351 7.03898 11.2351C3.31826 11.2351 0.30127 14.2521 0.30127 17.9728C0.30127 21.6935 3.31826 24.7105 7.03898 24.7105C7.98797 24.7105 8.89098 24.514 9.71037 24.1601C9.71246 24.1594 9.7142 24.1583 9.71594 24.1573C9.71768 24.1562 9.71943 24.1552 9.72152 24.1545C9.8107 24.1169 9.8985 24.0765 9.9849 24.0333L9.98629 24.0319C10.7625 23.6919 11.6181 23.5037 12.5197 23.5037C12.7524 23.5037 12.9824 23.5163 13.2081 23.54C13.2082 23.5399 13.2081 23.54 13.2081 23.54C15.0168 23.7365 16.5971 24.695 17.6185 26.0885C17.9195 25.1688 18.3752 24.3201 18.9563 23.5732C17.1964 23.4464 15.6635 22.5058 14.7312 21.1249Z"></path><g clip-path="url(#clip0)"><path d="M106.391 18.0021C106.391 11.3724 101.039 6 94.4389 6H88.4585C81.8581 6 76.5061 11.3724 76.5061 18.0021V30.0042H81.2845V18.0021C81.2845 14.0268 84.4941 10.8008 88.4585 10.8008H94.4347C98.395 10.8008 101.609 14.0226 101.609 18.0021V30.0042H106.391V18.0021Z"></path><path d="M149.432 6H142.258C135.653 6 130.301 11.3724 130.301 18.0021C130.301 24.6319 135.653 30.0042 142.258 30.0042H149.432C156.036 30.0042 161.388 24.6319 161.388 18.0021C161.388 11.3724 156.032 6 149.432 6ZM149.432 25.1992H142.258C138.297 25.1992 135.084 21.9774 135.084 17.9979C135.084 14.0183 138.293 10.7966 142.258 10.7966H149.432C153.392 10.7966 156.606 14.0183 156.606 17.9979C156.606 21.9774 153.392 25.1992 149.432 25.1992Z"></path><path d="M74.1151 25.1992H58.5736C55.4526 25.1992 52.804 23.1924 51.8171 20.3983H74.1151V17.9979C74.1151 17.1808 74.1868 16.3807 74.3175 15.5975H51.8171C52.804 12.8033 55.4526 10.7966 58.5736 10.7966H76.0383C77.1475 8.87458 78.6911 7.22773 80.5299 6H58.5736C51.969 6 46.6169 11.3724 46.6169 18.0021C46.6169 24.6276 51.969 30 58.5736 30H74.1151V25.1992Z"></path><path d="M120.74 6H115.958H102.369C104.212 7.22773 105.751 8.87458 106.861 10.8008H115.958V30H120.74V10.8008H129.838C130.947 8.87458 132.486 7.22773 134.329 6H120.74Z"></path><path d="M182.906 15.6017H169.756C168.436 15.6017 167.365 14.5264 167.365 13.2013C167.365 11.8762 168.436 10.8008 169.756 10.8008H188.882V6H169.756C165.796 6 162.582 9.22173 162.582 13.2013C162.582 17.1808 165.791 20.4025 169.756 20.4025H182.906C184.226 20.4025 185.297 21.4779 185.297 22.803C185.297 24.1281 184.226 25.2034 182.906 25.2034H161.852C160.743 27.1297 159.199 28.7765 157.361 30.0042H182.906C186.866 30.0042 190.08 26.7825 190.08 22.803C190.08 18.8234 186.866 15.6017 182.906 15.6017Z"></path></g><defs><clipPath id="clip0"><rect width="190" height="24" fill="white" transform="translate(0 6)"></rect></clipPath></defs></svg>
<div>
Entos Envision
</div>
</a>
</div>
<script>
AlphaOrbitalsExample.init('app')
</script>

View File

@@ -4,24 +4,25 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { BehaviorSubject } from 'rxjs';
import { debounceTime, skip } from 'rxjs/operators';
import { AlphaOrbital, Basis } from '../../extensions/alpha-orbitals/data-model';
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
import { createPluginAsync, DefaultPluginSpec } from '../../mol-plugin';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { createPluginAsync } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectSelector, StateTransformer } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition } from '../../mol-util/param-definition';
import { mountControls } from './controls';
import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
import { BehaviorSubject } from 'rxjs';
import { debounceTime, skip } from 'rxjs/operators';
import './index.html';
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
import { canComputeAlphaOrbitalsOnGPU } from '../../extensions/alpha-orbitals/gpu/compute';
import { PluginCommands } from '../../mol-plugin/commands';
require('mol-plugin-ui/skin/light.scss');
interface DemoInput {
@@ -49,18 +50,26 @@ type Selectors = {
}
export class AlphaOrbitalsExample {
plugin: PluginContext;
plugin: PluginUIContext;
async init(target: string | HTMLElement) {
const defaultSpec = DefaultPluginUISpec();
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...defaultSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
},
},
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
canvas3d: {
camera: {
helper: { axes: { name: 'off', params: { } } }
}
},
config: [
[PluginConfig.Viewport.ShowExpand, false],
[PluginConfig.Viewport.ShowControls, false],
@@ -71,7 +80,7 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
if (!canComputeAlphaOrbitalsOnGPU(this.plugin.canvas3d?.webgl)) {
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
@@ -165,7 +174,8 @@ export class AlphaOrbitalsExample {
kind,
relativeIsovalue: this.state.value.isoValue,
pickable: false,
xrayShaded: true
xrayShaded: true,
tryUseGpu: false
};
}

View File

@@ -5,7 +5,6 @@
*/
import { PluginUIComponent } from '../../mol-plugin-ui/base';
import * as React from 'react';
export class CustomToastMessage extends PluginUIComponent {
render() {

View File

@@ -0,0 +1,51 @@
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
import { Vec3 } from '../../mol-math/linear-algebra';
import { ColorTheme } from '../../mol-theme/color';
import { ThemeDataContext } from '../../mol-theme/theme';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export function CustomColorTheme(
ctx: ThemeDataContext,
props: PD.Values<{}>
): ColorTheme<{}> {
const { radius, center } = ctx.structure?.boundary.sphere!;
const radiusSq = Math.max(radius * radius, 0.001);
const scale = ColorTheme.PaletteScale;
return {
factory: CustomColorTheme,
granularity: 'vertex',
color: location => {
if (!isPositionLocation(location)) return ColorNames.black;
const dist = Vec3.squaredDistance(location.position, center);
const t = Math.min(dist / radiusSq, 1);
return ((t * scale) | 0) as Color;
},
palette: {
filter: 'nearest',
colors: [
ColorNames.red,
ColorNames.pink,
ColorNames.violet,
ColorNames.orange,
ColorNames.yellow,
ColorNames.green,
ColorNames.blue
]
},
props: props,
description: '',
};
}
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
name: 'basic-wrapper-custom-color-theme',
label: 'Custom Color Theme',
category: ColorTheme.Category.Misc,
factory: CustomColorTheme,
getParams: () => ({}),
defaultValues: { },
isApplicable: (ctx: ThemeDataContext) => true,
};

View File

@@ -86,7 +86,7 @@
// adjust this number to make the animation faster or slower
// requires to "restart" the animation if changed
BasicMolStarWrapper.animate.modelIndex.maxFPS = 30;
BasicMolStarWrapper.animate.modelIndex.targetFps = 30;
addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward());
addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward());
@@ -97,6 +97,7 @@
addHeader('Misc');
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme());
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
addHeader('Interactivity');

View File

@@ -4,38 +4,37 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { EmptyLoci } from '../../mol-model/loci';
import { StructureSelection } from '../../mol-model/structure';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { Script } from '../../mol-script/script';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { StripedResidues } from './coloring';
import { CustomToastMessage } from './controls';
import { CustomColorThemeProvider } from './custom-theme';
import './index.html';
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { Asset } from '../../mol-util/assets';
require('mol-plugin-ui/skin/light.scss');
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
class BasicWrapper {
plugin: PluginContext;
plugin: PluginUIContext;
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: {
// left: 'none'
}
},
components: {
@@ -44,6 +43,7 @@ class BasicWrapper {
});
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
}
@@ -82,13 +82,17 @@ class BasicWrapper {
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
}
private animateModelIndexTargetFps() {
return Math.max(1, this.animate.modelIndex.targetFps | 0);
}
animate = {
modelIndex: {
maxFPS: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
targetFps: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
stop: () => this.plugin.managers.animation.stop()
}
}
@@ -101,6 +105,13 @@ class BasicWrapper {
}
});
},
applyCustomTheme: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: CustomColorThemeProvider.name as any });
}
});
},
applyDefault: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {

View File

@@ -10,7 +10,7 @@ import { superpose } from '../../mol-model/structure/structure/util/superpositio
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { PluginContext } from '../../mol-plugin/context';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import Expression from '../../mol-script/language/expression';
import { Expression } from '../../mol-script/language/expression';
import { compile } from '../../mol-script/runtime/query/compiler';
import { StateObjectRef } from '../../mol-state';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';

View File

@@ -9,7 +9,7 @@ import { CifWriter } from '../../mol-io/writer/cif';
import * as S from './schemas';
// import { getCategoryInstanceProvider } from './utils'
export default function create(allData: any) {
export function createMapping(allData: any) {
const mols = Object.keys(allData);
const enc = CifWriter.createEncoder();
enc.startDataBlock(mols[0]);

View File

@@ -6,7 +6,7 @@
import express from 'express';
import fetch from 'node-fetch';
import createMapping from './mapping';
import { createMapping } from './mapping';
async function getMappings(id: string) {
const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`);

View File

@@ -5,7 +5,7 @@
*/
import fetch from 'node-fetch';
import createMapping from './mapping';
import { createMapping } from './mapping';
(async function () {
const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true');

View File

@@ -12,18 +12,18 @@
}
#app {
position: absolute;
left: 160px;
top: 100px;
width: 600px;
height: 600px;
border: 1px solid #ccc;
width: 100%;
height: 100%;
}
#controls {
position: absolute;
width: 150px;
top: 100px;
left: 780px;
bottom: 100px;
right: 50px;
z-index: 10;
font-family: sans-serif;
font-size: smaller;
}
#controls > button {
@@ -46,13 +46,13 @@
<div id="app"></div>
<script>
LightingDemo.init('app')
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3)
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' }));
addControl('4KTC', () => LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3));
addControl('5FJ5', () => LightingDemo.load({ url: 'https://models.rcsb.org/5FJ5.bcif', assemblyId: '1' }, 8, 1.8));
addControl('1UPN', () => LightingDemo.load({ url: 'https://models.rcsb.org/1UPN.bcif', assemblyId: '1' }, 7, 1.6));
addControl('1RB8', () => LightingDemo.load({ url: 'https://models.rcsb.org/1RB8.bcif', assemblyId: '1' }, 6, 1.3));
addSeparator()

View File

@@ -5,12 +5,13 @@
*/
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import './index.html';
import { Asset } from '../../mol-util/assets';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
@@ -24,12 +25,11 @@ const Canvas3DPresets = {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.8 } }
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.1 } }
},
renderer: {
ambientIntensity: 1,
lightIntensity: 0,
style: { name: 'flat', params: {} }
}
},
occlusion: <Preset> {
@@ -37,12 +37,11 @@ const Canvas3DPresets = {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
outline: { name: 'off', params: { } }
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
style: { name: 'matte', params: {} }
}
},
standard: <Preset> {
@@ -54,25 +53,31 @@ const Canvas3DPresets = {
outline: { name: 'off', params: { } }
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
style: { name: 'matte', params: {} }
}
}
};
type Canvas3DPreset = keyof typeof Canvas3DPresets
class LightingDemo {
plugin: PluginContext;
plugin: PluginUIContext;
private radius = 5;
private bias = 1.1;
private preset: Canvas3DPreset = 'illustrative';
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
},
},
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
}
});
@@ -82,6 +87,10 @@ class LightingDemo {
setPreset(preset: Canvas3DPreset) {
const props = Canvas3DPresets[preset];
if (props.postprocessing.occlusion?.name === 'on') {
props.postprocessing.occlusion.params.radius = this.radius;
props.postprocessing.occlusion.params.bias = this.bias;
}
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
...props,
multiSample: {
@@ -99,7 +108,7 @@ class LightingDemo {
}});
}
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
async load({ url, format = 'mmcif', isBinary = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {
await this.plugin.clear();
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
@@ -111,7 +120,11 @@ class LightingDemo {
if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' });
const ligand = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'ligand');
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick' });
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
this.radius = radius;
this.bias = bias;
this.setPreset(this.preset);
}
}

View File

@@ -147,7 +147,7 @@
// adjust this number to make the animation faster or slower
// requires to "restart" the animation if changed
PluginWrapper.animate.modelIndex.maxFPS = 30;
PluginWrapper.animate.modelIndex.targetFps = 30;
addControl('Play To End', () => PluginWrapper.animate.modelIndex.onceForward());
addControl('Play To Start', () => PluginWrapper.animate.modelIndex.onceBackward());

View File

@@ -6,14 +6,15 @@
import * as ReactDOM from 'react-dom';
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { PluginState } from '../../mol-plugin/state';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
@@ -40,13 +41,13 @@ class MolStarProteopediaWrapper {
modelInfo: this._ev<ModelInfo>()
};
plugin: PluginContext;
plugin: PluginUIContext;
init(target: string | HTMLElement, options?: {
customColorList?: number[]
}) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginUISpec(),
animations: [
AnimateModelIndex
],
@@ -271,13 +272,17 @@ class MolStarProteopediaWrapper {
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
}
private animateModelIndexTargetFps() {
return Math.max(1, this.animate.modelIndex.targetFps | 0);
}
animate = {
modelIndex: {
maxFPS: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
targetFps: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
stop: () => this.plugin.managers.animation.stop()
}
}

View File

@@ -4,14 +4,13 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { PluginUIContext } from '../../../mol-plugin-ui/context';
import { PluginContextContainer } from '../../../mol-plugin-ui/plugin';
import { TransformUpdaterControl } from '../../../mol-plugin-ui/state/update-transform';
import { PluginContext } from '../../../mol-plugin/context';
import { StateElements } from '../helpers';
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
export function volumeStreamingControls(plugin: PluginUIContext, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={plugin}>
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
</PluginContextContainer>, parent);

View File

@@ -7,8 +7,9 @@
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
import { Grid } from '../../mol-model/volume';
import { SphericalBasisOrder } from './spherical-functions';
import { Box3D } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms } from '../../mol-util/array';
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
import { ModelFormat } from '../../mol-model-formats/format';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
@@ -59,6 +60,17 @@ export interface CubeGrid {
isovalues?: { negative?: number; positive?: number };
}
export type CubeGridFormat = ModelFormat<CubeGrid>;
// eslint-disable-next-line
export function CubeGridFormat(grid: CubeGrid): CubeGridFormat {
return { name: 'custom grid', kind: 'cube-grid', data: grid };
}
export function isCubeGridData(f: ModelFormat): f is CubeGridFormat {
return f.kind === 'cube-grid';
}
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const geometry = params.basis.atoms.map(a => a.center);
const { gridSpacing: spacing, boxExpand: expand } = params;
@@ -95,7 +107,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const BohrToAngstromFactor = 0.529177210859;
export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[]) {
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
const boxSize = Box3D.size(Vec3(), gridInfo.box);
const boxOrigin = Vec3.clone(gridInfo.box.min);
@@ -122,7 +134,7 @@ export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrd
stats: {
min: arrayMin(values),
max: arrayMax(values),
mean: arrayMax(values),
mean: arrayMean(values),
sigma: arrayRms(values),
},
};

View File

@@ -5,10 +5,11 @@
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
export function createSphericalCollocationDensityGrid(
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
@@ -17,9 +18,9 @@ export function createSphericalCollocationDensityGrid(
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(webgl!, cubeGrid, orbitals, ctx);
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
// console.timeEnd('gpu');
} else {
throw new Error('Missing OES_texture_float WebGL extension.');

View File

@@ -4,46 +4,72 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { QuadSchema, QuadValues } from '../../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../../mol-gl/renderable';
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../../mol-gl/renderable/schema';
import { ShaderCode } from '../../../mol-gl/shader-code';
import quad_vert from '../../../mol-gl/shader/quad.vert';
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
import { RuntimeContext } from '../../../mol-task';
import { ValueCell } from '../../../mol-util';
import { arrayMin } from '../../../mol-util/array';
import { isLittleEndian } from '../../../mol-util/is-little-endian';
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
import shader_frag from './shader.frag';
import { MAIN, UTILS } from './shader.frag';
const AlphaOrbitalsSchema = {
...QuadSchema,
uDimensions: UniformSpec('v3'),
uMin: UniformSpec('v3'),
uDelta: UniformSpec('v3'),
const Schema = {
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
uWidth: UniformSpec('f'),
uNCenters: UniformSpec('i'),
uNAlpha: UniformSpec('i'),
uNCoeff: UniformSpec('i'),
uMaxCoeffs: UniformSpec('i'),
uLittleEndian: UniformSpec('b'),
uDensity: UniformSpec('b'),
uOccupancy: UniformSpec('f'),
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
};
type AlphaOrbitalsSchema = Values<typeof AlphaOrbitalsSchema>
const AlphaOrbitalsName = 'alpha-orbitals';
const AlphaOrbitalsTex0 = 'alpha-orbitals-0';
const AlphaOrbitalsTex1 = 'alpha-orbitals-1';
const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag);
type AlphaOrbitalsRenderable = ComputeRenderable<AlphaOrbitalsSchema>
const Orbitals = createGrid3dComputeRenderable({
schema: Schema,
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'v',
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
return createTextureData(params.grid, params.orbital);
}
});
const Density = createGrid3dComputeRenderable({
schema: {
...Schema,
uOccupancy: UniformSpec('f'),
},
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'current + uOccupancy * v * v',
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return {
...createTextureData(params.grid, params.orbitals[0]),
uOccupancy: 0
};
},
cumulative: {
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return params.orbitals.filter(o => o.occupancy !== 0);
},
update({ grid }, state: AlphaOrbital, values) {
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
}
}
});
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
return Orbitals(ctx, webgl, grid, { grid, orbital });
}
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
return Density(ctx, webgl, grid, { grid, orbitals });
}
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
const alpha = new Float32Array(alphaOrbitals.length);
@@ -62,7 +88,7 @@ function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrde
return alpha;
}
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let centerCount = 0;
@@ -131,179 +157,14 @@ function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
}
}
return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff };
}
function createAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
const data = createTextureData(grid, orbital);
const [nx, ny, nz] = grid.dimensions;
const width = Math.ceil(Math.sqrt(nx * ny * nz));
if (!ctx.namedFramebuffers[AlphaOrbitalsName]) {
ctx.namedFramebuffers[AlphaOrbitalsName] = ctx.resources.framebuffer();
}
if (!ctx.namedTextures[AlphaOrbitalsTex0]) {
ctx.namedTextures[AlphaOrbitalsTex0] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (!ctx.namedTextures[AlphaOrbitalsTex1]) {
ctx.namedTextures[AlphaOrbitalsTex1] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const values: AlphaOrbitalsSchema = {
...QuadValues,
uDimensions: ValueCell.create(grid.dimensions),
uMin: ValueCell.create(grid.box.min),
uDelta: ValueCell.create(grid.delta),
uWidth: ValueCell.create(width),
uNCenters: ValueCell.create(data.nCenters),
uNAlpha: ValueCell.create(data.nAlpha),
uNCoeff: ValueCell.create(data.nCoeff),
uMaxCoeffs: ValueCell.create(data.maxCoeffs),
tCenters: ValueCell.create({ width: data.nCenters, height: 1, array: data.centers }),
tInfo: ValueCell.create({ width: data.nCenters, height: 1, array: data.info }),
tCoeff: ValueCell.create({ width: data.nCoeff, height: 1, array: data.coeff }),
tAlpha: ValueCell.create({ width: data.nAlpha, height: 1, array: data.alpha }),
uLittleEndian: ValueCell.create(isLittleEndian()),
uDensity: ValueCell.create(false),
uOccupancy: ValueCell.create(0),
tCumulativeSum: ValueCell.create(ctx.namedTextures[AlphaOrbitalsTex1])
return {
uNCenters: centerCount,
uNAlpha: baseCount,
uNCoeff: coeffCount,
uMaxCoeffs: maxCoeffs,
tCenters: { width: centerCount, height: 1, array: centers },
tInfo: { width: centerCount, height: 1, array: info },
tCoeff: { width: coeffCount, height: 1, array: coeff },
tAlpha: { width: baseCount, height: 1, array: alpha },
};
const schema = { ...AlphaOrbitalsSchema };
if (!ctx.isWebGL2) {
// workaround for webgl1 limitation that loop counters need to be `const`
(schema.uNCenters as any) = DefineSpec('number');
(schema.uMaxCoeffs as any) = DefineSpec('number');
}
const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function getAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
if (ctx.namedComputeRenderables[AlphaOrbitalsName]) {
const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values as AlphaOrbitalsSchema;
const data = createTextureData(grid, orbital);
const [nx, ny, nz] = grid.dimensions;
const width = Math.ceil(Math.sqrt(nx * ny * nz));
ValueCell.update(v.uDimensions, grid.dimensions);
ValueCell.update(v.uMin, grid.box.min);
ValueCell.update(v.uDelta, grid.delta);
ValueCell.updateIfChanged(v.uWidth, width);
ValueCell.updateIfChanged(v.uNCenters, data.nCenters);
ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha);
ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff);
ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs);
ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers });
ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info });
ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff });
ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha });
ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian());
ValueCell.updateIfChanged(v.uDensity, false);
ValueCell.updateIfChanged(v.uOccupancy, 0);
ValueCell.updateIfChanged(v.tCumulativeSum, ctx.namedTextures[AlphaOrbitalsTex1]);
ctx.namedComputeRenderables[AlphaOrbitalsName].update();
} else {
ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, grid, orbital);
}
return ctx.namedComputeRenderables[AlphaOrbitalsName];
}
export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
const [nx, ny, nz] = grid.dimensions;
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbital);
const width = renderable.values.uWidth.ref.value;
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
webgl.namedTextures[AlphaOrbitalsTex0].define(width, width);
webgl.namedTextures[AlphaOrbitalsTex0].attachFramebuffer(framebuffer, 'color0');
const { gl, state } = webgl;
framebuffer.bind();
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
renderable.render();
const array = new Uint8Array(width * width * 4);
webgl.readPixels(0, 0, width, width, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
}
export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) {
return !!webgl?.extensions.textureFloat;
}
export async function gpuComputeAlphaOrbitalsDensityGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[], ctx: RuntimeContext) {
await ctx.update({ message: 'Initializing...', isIndeterminate: true });
const [nx, ny, nz] = grid.dimensions;
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbitals[0]);
const width = renderable.values.uWidth.ref.value;
if (!webgl.namedFramebuffers[AlphaOrbitalsName]) {
webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
const tex = [webgl.namedTextures[AlphaOrbitalsTex0], webgl.namedTextures[AlphaOrbitalsTex1]];
tex[0].define(width, width);
tex[1].define(width, width);
const values = renderable.values as AlphaOrbitalsSchema;
const { gl, state } = webgl;
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.clearColor(0, 0, 0, 0);
tex[0].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
tex[1].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
ValueCell.update(values.uDensity, true);
const nonZero = orbitals.filter(o => o.occupancy !== 0);
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: nonZero.length });
for (let i = 0; i < nonZero.length; i++) {
const alpha = getNormalizedAlpha(grid.params.basis, nonZero[i].alpha, grid.params.sphericalOrder);
ValueCell.update(values.uOccupancy, nonZero[i].occupancy);
ValueCell.update(values.tCumulativeSum, tex[(i + 1) % 2]);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
renderable.update();
renderable.render();
if (i !== nonZero.length - 1 && ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
const array = new Uint8Array(width * width * 4);
webgl.readPixels(0, 0, width, width, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
}

View File

@@ -4,165 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform vec2 uQuadShift;
uniform vec3 uDimensions;
uniform vec3 uMin;
uniform vec3 uDelta;
uniform sampler2D tCenters;
uniform sampler2D tInfo;
uniform sampler2D tCoeff;
uniform sampler2D tAlpha;
uniform float uWidth;
#ifndef uNCenters
uniform int uNCenters;
#endif
uniform int uNCoeff;
uniform int uNAlpha;
uniform bool uDensity;
uniform float uOccupancy;
uniform sampler2D tCumulativeSum;
uniform bool uLittleEndian;
//////////////////////////////////////////////////////////
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
// MIT License, Copyright (c) 2020 Equinor
float shiftRight (float v, float amt) {
v = floor(v) + 0.5;
return floor(v / exp2(amt));
}
float shiftLeft (float v, float amt) {
return floor(v * exp2(amt) + 0.5);
}
float maskLast (float v, float bits) {
return mod(v, shiftLeft(1.0, bits));
}
float extractBits (float num, float from, float to) {
from = floor(from + 0.5); to = floor(to + 0.5);
return maskLast(shiftRight(num, from), to - from);
}
vec4 floatToRgba(float texelFloat) {
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
texelFloat = abs(texelFloat);
float exponent = floor(log2(texelFloat));
float biased_exponent = exponent + 127.0;
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
float t = biased_exponent / 2.0;
float last_bit_of_biased_exponent = fract(t) * 2.0;
float remaining_bits_of_biased_exponent = floor(t);
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
return (
uLittleEndian
? vec4(byte4, byte3, byte2, byte1)
: vec4(byte1, byte2, byte3, byte4)
);
}
///////////////////////////////////////////////////////
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
// BSD 3-Clause License
//
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
ivec4 floatsToBytes(vec4 inputFloats) {
ivec4 bytes = ivec4(inputFloats * 255.0);
return (
uLittleEndian
? bytes.abgr
: bytes
);
}
// Break the four bytes down into an array of 32 bits.
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
float acc = float(bytes[channelIndex]);
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
float powerOfTwo = exp2(float(indexInByte));
bool bit = acc >= powerOfTwo;
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
acc = mod(acc, powerOfTwo);
}
}
}
// Compute the exponent of the 32-bit float.
float getExponent(bool bits[32]) {
const int startIndex = 1;
const int bitStringLength = 8;
const int endBeforeIndex = startIndex + bitStringLength;
float acc = 0.0;
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Compute the mantissa of the 32-bit float.
float getMantissa(bool bits[32], bool subnormal) {
const int startIndex = 9;
const int bitStringLength = 23;
const int endBeforeIndex = startIndex + bitStringLength;
// Leading/implicit/hidden bit convention:
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
float acc = float(!subnormal) * exp2(float(bitStringLength));
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Parse the float from its 32 bits.
float bitsToFloat(bool bits[32]) {
float signBit = float(bits[0]) * -2.0 + 1.0;
float exponent = getExponent(bits);
bool subnormal = abs(exponent - 0.0) < 0.01;
float mantissa = getMantissa(bits, subnormal);
float exponentBias = 127.0;
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
}
float rgbaToFloat(vec4 texelRGBA) {
ivec4 rgbaBytes = floatsToBytes(texelRGBA);
bool bits[32];
bytesToBits(rgbaBytes, bits);
return bitsToFloat(bits);
}
///////////////////////////////////////////////////////
export const UTILS = `
float L1(vec3 p, float a0, float a1, float a2) {
return a0 * p.z + a1 * p.x + a2 * p.y;
}
@@ -213,12 +55,10 @@ float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, flo
}
float alpha(float offset, float f) {
#ifdef uMaxCoeffs
#ifdef WEBGL1
// in webgl1, the value is in the alpha channel!
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
#endif
#ifndef uMaxCoeffs
#else
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
#endif
}
@@ -249,7 +89,7 @@ float Y(int L, vec3 X, float aO, float fA) {
return 0.0;
}
#ifndef uMaxCoeffs
#ifndef WEBGL1
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
for (int i = start; i < end; i++) {
@@ -258,9 +98,7 @@ float Y(int L, vec3 X, float aO, float fA) {
}
return gauss;
}
#endif
#ifdef uMaxCoeffs
#else
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
int o = start;
@@ -274,28 +112,13 @@ float Y(int L, vec3 X, float aO, float fA) {
return gauss;
}
#endif
`;
float intDiv(float a, float b) { return float(int(a) / int(b)); }
float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
void main(void) {
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
// axis order fast to slow Z, Y, X
// TODO: support arbitrary axis orders?
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
float j = intMod(kk, uDimensions.y);
float i = intDiv(kk, uDimensions.y);
vec3 xyz = uMin + uDelta * vec3(i, j, k);
export const MAIN = `
float fCenter = 1.0 / float(uNCenters - 1);
float fCoeff = 1.0 / float(uNCoeff - 1);
float fA = 1.0 / float(uNAlpha - 1);
// gl_FragColor = floatToRgba(offset);
// return;
float v = 0.0;
for (int i = 0; i < uNCenters; i++) {
@@ -319,13 +142,4 @@ void main(void) {
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
}
if (uDensity) {
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)));
gl_FragColor = floatToRgba(current + uOccupancy * v * v);
} else {
gl_FragColor = floatToRgba(v);
}
}
`;

View File

@@ -7,11 +7,14 @@
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { sphericalCollocation } from './collocation';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
// setDebugMode(true);
export function createSphericalCollocationGrid(
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
@@ -20,9 +23,9 @@ export function createSphericalCollocationGrid(
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cubeGrid, orbital);
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
// console.timeEnd('gpu');
} else {
// console.time('cpu');

View File

@@ -17,8 +17,9 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
import { StateTransformer } from '../../mol-state';
import { Theme } from '../../mol-theme/theme';
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
import { AlphaOrbital, Basis, CubeGrid, CubeGridFormat, isCubeGridData } from './data-model';
import { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
@@ -49,9 +50,43 @@ const CreateOrbitalVolumeParamBase = {
{ atomCount: 25, spacing: 0.4 },
{ atomCount: 0, spacing: 0.35 },
]
}),
clampValues: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
on: PD.Group({
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
})
})
};
function clampData(matrix: Tensor.Data, min: number, max: number) {
for (let i = 0, _i = matrix.length; i < _i; i++) {
const v = matrix[i];
if (v < min) matrix[i] = min;
else if (v > max) matrix[i] = max;
}
}
function clampGrid(data: CubeGrid, v: number) {
const grid = data.grid;
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
// clamp values for better direct volume resolution
// current implementation uses Byte array for values
// if this is not enough, update mol* to use float
// textures instead
if (grid.stats.min < min || grid.stats.max > max) {
clampData(data.grid.cells.data, min, max);
if (grid.stats.min < min) {
(grid.stats.min as number) = min;
}
if (grid.stats.max > max) {
(grid.stats.max as number) = max;
}
}
}
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-volume',
display: 'Orbital Volume',
@@ -79,11 +114,15 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
sourceData: CubeGridFormat(data),
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
@@ -107,11 +146,15 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
sourceData: CubeGridFormat(data),
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
@@ -129,7 +172,8 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
color: PD.Color(ColorNames.blue),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
xrayShaded: PD.Boolean(false),
pickable: PD.Boolean(true)
pickable: PD.Boolean(true),
tryUseGpu: PD.Boolean(true)
}
})({
canAutoUpdate() {
@@ -147,7 +191,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
await repr.createOrUpdate(props, a.data).runInContext(ctx);
repr.setState({ pickable: srcParams.pickable });
return new PluginStateObject.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
return new PluginStateObject.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
});
},
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
@@ -157,6 +201,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
b.data.repr.setState({ pickable: srcParams.pickable });
b.description = VolumeRepresentation3DHelpers.getDescription(props);
return StateTransformer.UpdateResult.Updated;
@@ -165,9 +210,9 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
});
function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Data, params: StateTransformer.Params<typeof CreateOrbitalRepresentation3D>) {
if (volume.data.sourceData.kind !== 'alpha-orbitals') throw new Error('Invalid data source kind.');
if (!isCubeGridData(volume.data.sourceData)) throw new Error('Invalid data source kind.');
const { isovalues } = volume.data.sourceData.data as CubeGrid;
const { isovalues } = volume.data.sourceData.data;
if (!isovalues) throw new Error('Isovalues are not computed.');
const value = isovalues[params.kind];
@@ -186,7 +231,7 @@ function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Da
colorParams: { value: params.color }
} : {
type: 'isosurface',
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded, tryUseGpu: params.tryUseGpu },
color: 'uniform',
colorParams: { value: params.color }
});

View File

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

View File

@@ -121,7 +121,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
const repr = MembraneOrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => MembraneOrientationParams);
await repr.createOrUpdate(params, a.data).runInContext(ctx);
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: 'Membrane Orientation' });
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: 'Membrane Orientation' });
});
},
update({ a, b, newParams }, plugin: PluginContext) {
@@ -129,6 +129,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
const props = { ...b.data.repr.props, ...newParams };
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
return StateTransformer.UpdateResult.Updated;
});
},

View File

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

View File

@@ -9,8 +9,6 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
import { Structure } from '../../mol-model/structure';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
import { MembraneOrientation } from './prop';
import { ThemeRegistryContext } from '../../mol-theme/theme';
@@ -27,21 +25,13 @@ import { MembraneOrientationProvider } from './prop';
import { MarkerActions } from '../../mol-util/marker-action';
import { lociLabel } from '../../mol-theme/label';
import { ColorNames } from '../../mol-util/color/names';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
const SharedParams = {
color: PD.Color(ColorNames.lightgrey),
radiusFactor: PD.Numeric(0.8333, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
radiusFactor: PD.Numeric(1.2, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
};
const BilayerSpheresParams = {
...Spheres.Params,
...SharedParams,
sphereSize: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }, { description: 'Size of spheres that represent membrane planes' }),
density: PD.Numeric(1, { min: 0.25, max: 10, step: 0.25 }, { description: 'Distance between spheres'})
};
export type BilayerSpheresParams = typeof BilayerSpheresParams
export type BilayerSpheresProps = PD.Values<BilayerSpheresParams>
const BilayerPlanesParams = {
...Mesh.Params,
...SharedParams,
@@ -61,13 +51,11 @@ export type BilayerRimsParams = typeof BilayerRimsParams
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
const MembraneOrientationVisuals = {
'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
};
export const MembraneOrientationParams = {
...BilayerSpheresParams,
...BilayerPlanesParams,
...BilayerRimsParams,
visuals: PD.MultiSelect(['bilayer-planes', 'bilayer-rims'], PD.objectToOptions(MembraneOrientationVisuals)),
@@ -91,9 +79,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation
factory: MembraneOrientationRepresentation,
getParams: getMembraneOrientationParams,
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
defaultColorTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.elementCount > 0
defaultColorTheme: { name: 'shape-group' },
defaultSizeTheme: { name: 'shape-group' },
isApplicable: (structure: Structure) => structure.elementCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
detach: (data) => MembraneOrientationProvider.ref(data, false)
}
});
function membraneLabel(data: Structure) {
@@ -101,16 +93,16 @@ function membraneLabel(data: Structure) {
}
function getBilayerRims(ctx: RuntimeContext, data: Structure, props: BilayerRimsProps, shape?: Shape<Lines>): Shape<Lines> {
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
const { planePoint1: p1, planePoint2: p2, centroid, radius } = MembraneOrientationProvider.get(data).value!;
const scaledRadius = props.radiusFactor * radius;
const builder = LinesBuilder.create(128, 64, shape?.geometry);
getLayerCircle(builder, p1, centroid, normal, scaledRadius, props);
getLayerCircle(builder, p2, centroid, normal, scaledRadius, props);
getLayerCircle(builder, p1, centroid, scaledRadius, props);
getLayerCircle(builder, p2, centroid, scaledRadius, props);
return Shape.create('Bilayer rims', data, builder.getLines(), () => props.color, () => props.linesSize, () => membraneLabel(data));
}
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
const circle = getCircle(p, centroid, normal, radius);
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
const circle = getCircle(p, centroid, radius);
const { indices, vertices } = circle;
for (let j = 0, jl = indices.length; j < jl; j += 3) {
if (props.dashedLines && j % 2 === 1) continue; // draw every other segment to get dashes
@@ -127,8 +119,13 @@ function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal:
}
const tmpMat = Mat4();
function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
Mat4.targetTo(tmpMat, p, centroid, normal);
const tmpV = Vec3();
function getCircle(p: Vec3, centroid: Vec3, radius: number) {
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, p, centroid)) === 0) {
Mat4.targetTo(tmpMat, p, centroid, Vec3.unitY);
} else {
Mat4.targetTo(tmpMat, p, centroid, Vec3.unitX);
}
Mat4.setTranslation(tmpMat, p);
Mat4.mul(tmpMat, tmpMat, Mat4.rotX90);
@@ -137,42 +134,17 @@ function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
}
function getBilayerPlanes(ctx: RuntimeContext, data: Structure, props: BilayerPlanesProps, shape?: Shape<Mesh>): Shape<Mesh> {
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
const { planePoint1: p1, planePoint2: p2, centroid, radius } = MembraneOrientationProvider.get(data).value!;
const state = MeshBuilder.createState(128, 64, shape && shape.geometry);
const scaledRadius = props.radiusFactor * radius;
getLayerPlane(state, p1, centroid, normal, scaledRadius);
getLayerPlane(state, p2, centroid, normal, scaledRadius);
getLayerPlane(state, p1, centroid, scaledRadius);
getLayerPlane(state, p2, centroid, scaledRadius);
return Shape.create('Bilayer planes', data, MeshBuilder.getMesh(state), () => props.color, () => 1, () => membraneLabel(data));
}
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
const circle = getCircle(p, centroid, normal, radius);
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, radius: number) {
const circle = getCircle(p, centroid, radius);
state.currentGroup = 0;
MeshBuilder.addPrimitive(state, Mat4.id, circle);
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
}
function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> {
const { density } = props;
const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!;
const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius);
const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry);
getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius);
getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius);
return Shape.create('Bilayer spheres', data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data));
}
function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) {
Vec3.normalize(normalVector, normalVector);
const d = -Vec3.dot(normalVector, point);
const rep = Vec3();
for (let i = -1000, il = 1000; i < il; i += density) {
for (let j = -1000, jl = 1000; j < jl; j += density) {
Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]);
if (Vec3.squaredDistance(rep, point) < sqRadius) {
spheresBuilder.add(rep[0], rep[1], rep[2], 0);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -46,10 +46,9 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75,
minLabel: 'Min', maxLabel: 'Max', valueLabel: (i: number) => `${i + 1}`,
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
}
}});
}}, { minLabel: 'Min', maxLabel: 'Max' });
legend = palette.legend;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {
@@ -89,7 +88,6 @@ export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGen
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
!!CellPackInfoProvider.get(ctx.structure).value
);
}

View File

@@ -9,7 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Color } from '../../../mol-util/color';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { StructureElement, Model } from '../../../mol-model/structure';
import { StructureElement, Model, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { CellPackInfoProvider } from '../property';
@@ -37,9 +37,12 @@ export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Valu
}
color = (location: Location): Color => {
return StructureElement.Location.is(location)
? modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!
: DefaultColor;
if (StructureElement.Location.is(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
} else if (Bond.isLocation(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
}
return DefaultColor;
};
} else {
color = () => DefaultColor;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -396,21 +396,25 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
}
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
const builder = Structure.Builder({ label: name });
const units: Unit[] = [];
let offsetInvariantId = 0;
let offsetChainGroupId = 0;
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
let maxInvariantId = 0;
let maxChainGroupId = 0;
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId;
const chainGroupId = u.chainGroupId + offsetChainGroupId;
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
}
offsetInvariantId += maxInvariantId + 1;
offsetChainGroupId += maxChainGroupId + 1;
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = builder.getStructure();
const structure = Structure.create(units);
for( let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}

View File

@@ -181,6 +181,6 @@ export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramid
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ConfalPyramidsProvider.descriptor, false)
detach: (data) => data.structure && ConfalPyramidsProvider.ref(data.structure.models[0], false)
}
};

View File

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

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import msgpackDecode from '../../mol-io/common/msgpack/decode';
import { decodeMsgPack } from '../../mol-io/common/msgpack/decode';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { inflate } from '../../mol-util/zip/zip';
@@ -41,7 +41,7 @@ export async function getG3dHeader(ctx: PluginContext, urlOrData: string | Uint8
for (; last >= 0; last--) {
if (data[last] !== 0) break;
}
const header = msgpackDecode(data.slice(0, last + 1));
const header = decodeMsgPack(data.slice(0, last + 1));
return header;
}
@@ -53,7 +53,7 @@ export async function getG3dDataBlock(ctx: PluginContext, header: G3dHeader, url
return {
header,
resolution,
data: msgpackDecode(unzipped)
data: decodeMsgPack(unzipped)
};
}

View File

@@ -16,7 +16,7 @@ import { MoleculeType } from '../../mol-model/structure/model/types';
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { CustomPropSymbol } from '../../mol-script/language/symbol';
import Type from '../../mol-script/language/type';
import { Type } from '../../mol-script/language/type';
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
import { RuntimeContext, Task } from '../../mol-task';
import { objectForEach } from '../../mol-util/object';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import React from 'react';
import { merge } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';

View File

@@ -6,7 +6,7 @@
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
import { Location } from '../../../mol-model/location';
import { StructureElement } from '../../../mol-model/structure';
import { Bond, StructureElement } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
@@ -46,11 +46,16 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) {
const getIssues = StructureQualityReport.getIssues;
const l = StructureElement.Location.create(ctx.structure);
if (props.type.name === 'issue-count') {
color = (location: Location) => {
if (StructureElement.Location.is(location)) {
return ValidationColors[Math.min(3, getIssues(location).length) + 1];
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return ValidationColors[Math.min(3, getIssues(l).length) + 1];
}
return ValidationColors[0];
};
@@ -59,6 +64,10 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
color = (location: Location) => {
if (StructureElement.Location.is(location) && getIssues(location).indexOf(issue) >= 0) {
return ValidationColors[4];
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return ValidationColors[Math.min(3, getIssues(l).length) + 1];
}
return ValidationColors[0];
};
@@ -70,6 +79,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
return {
factory: StructureQualityReportColorTheme,
granularity: 'group',
preferSmoothing: true,
color: color,
props: props,
description: 'Assigns residue colors according to the number of quality issues or a specific quality issue. Data from wwPDB Validation Report, obtained via PDBe.',
@@ -105,6 +115,6 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
detach: (data) => data.structure && StructureQualityReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -13,7 +13,7 @@ import { Model, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-m
import { residueIdFields } from '../../../mol-model/structure/export/categories/atom_site';
import { StructureElement, CifExportContext, Structure } from '../../../mol-model/structure/structure';
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
import Type from '../../../mol-script/language/type';
import { Type } from '../../../mol-script/language/type';
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { arraySetAdd } from '../../../mol-util/array';

View File

@@ -124,7 +124,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams);
await repr.createOrUpdate(params, a.data).runInContext(ctx);
const { type, kind, symbol } = assemblySymmetry;
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: kind, description: `${type} (${symbol})` });
});
},
update({ a, b, newParams }, plugin: PluginContext) {
@@ -138,6 +138,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
}
const props = { ...b.data.repr.props, ...newParams };
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
const { type, kind, symbol } = assemblySymmetry;
b.label = kind;
b.description = `${type} (${symbol})`;

View File

@@ -9,7 +9,7 @@ import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
import { Color } from '../../../mol-util/color';
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
import { Unit, StructureElement, StructureProperties, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
@@ -50,6 +50,8 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
const clusters = assemblySymmetry?.value?.clusters;
if (clusters?.length && ctx.structure) {
const l = StructureElement.Location.create(ctx.structure);
const clusterByMember = new Map<string, number>();
for (let i = 0, il = clusters.length; i < il; ++i) {
const { members } = clusters[i]!;
@@ -67,12 +69,20 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
legend = palette.legend;
const _emptyList: any[] = [];
const getColor = (location: StructureElement.Location) => {
const { assembly } = location.unit.conformation.operator;
const asymId = getAsymId(location.unit)(location);
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
const { assembly } = location.unit.conformation.operator;
const asymId = getAsymId(location.unit)(location);
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
return getColor(location);
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return getColor(l);
}
return DefaultColor;
};
@@ -99,6 +109,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
detach: (data) => data.structure && AssemblySymmetryProvider.ref(data.structure, false)
}
};

View File

@@ -5,7 +5,7 @@
*/
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphql/types';
import query from '../graphql/symmetry.gql';
import { symmetry_gql } from '../graphql/symmetry.gql';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
@@ -66,7 +66,7 @@ export namespace AssemblySymmetry {
assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
entry_id: structure.units[0].model.entryId
};
const result = await client.request(ctx.runtime, query, variables);
const result = await client.request(ctx.runtime, symmetry_gql, variables);
let value: AssemblySymmetryDataValue = [];
if (!result.data.assembly?.rcsb_struct_symmetry) {
@@ -124,16 +124,20 @@ export function getSymmetrySelectParam(structure?: Structure) {
if (structure) {
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
if (assemblySymmetryData) {
const options: [number, string][] = [];
const options: [number, string][] = [
[-1, 'Off']
];
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
const { symbol, kind } = assemblySymmetryData[i];
if (symbol !== 'C1') {
options.push([ i, `${i + 1}: ${symbol} ${kind}` ]);
}
}
if (options.length) {
if (options.length > 1) {
param.options = options;
param.defaultValue = options[0][0];
param.defaultValue = options[1][0];
} else {
options.length = 0;
}
}
}

View File

@@ -4,7 +4,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
@@ -74,7 +73,6 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
const structure = this.pivot.cell.obj?.data;
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
params.serverUrl.isHidden = true;
params.symmetryIndex.options = [[-1, 'Off'], ...params.symmetryIndex.options];
return params;
}

View File

@@ -1,4 +1,4 @@
export default /* GraphQL */ `
export const symmetry_gql = /* GraphQL */ `
query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
assembly(assembly_id: $assembly_id, entry_id: $entry_id) {
rcsb_struct_symmetry {

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color, ColorScale } from '../../../../mol-util/color';
import { StructureElement, Model } from '../../../../mol-model/structure';
import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -37,13 +37,19 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
if (validationReport?.value && model) {
const { rsrz, rscc } = validationReport.value;
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const getColor = (element: ElementIndex) => {
const rsrzValue = rsrz.get(residueIndex[element]);
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
const rsccValue = rscc.get(residueIndex[element]);
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
return DefaultColor;
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const rsrzValue = rsrz.get(residueIndex[location.element]);
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
const rsccValue = rscc.get(residueIndex[location.element]);
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
return DefaultColor;
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};
@@ -52,6 +58,7 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
return {
factory: DensityFitColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
props,
contextHash,
@@ -70,6 +77,6 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromXray(ctx.structure.models[0]) && Model.probablyHasDensityMap(ctx.structure.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color } from '../../../../mol-util/color';
import { StructureElement } from '../../../../mol-model/structure';
import { Bond, ElementIndex, StructureElement } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -59,31 +59,35 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const { polymerType } = model.atomicHierarchy.derived.residue;
const ignore = new Set(props.ignore);
const getColor = (element: ElementIndex) => {
const rI = residueIndex[element];
const value = geometryIssues.get(rI);
if (value === undefined) return DefaultColor;
let count = SetUtils.differenceSize(value, ignore);
if (count > 0 && polymerType[rI] === PolymerType.NA) {
count = 0;
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
}
switch (count) {
case undefined: return DefaultColor;
case 0: return NoIssuesColor;
case 1: return OneIssueColor;
case 2: return TwoIssuesColor;
default: return ThreeOrMoreIssuesColor;
}
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const { element } = location;
const rI = residueIndex[element];
const value = geometryIssues.get(rI);
if (value === undefined) return DefaultColor;
let count = SetUtils.differenceSize(value, ignore);
if (count > 0 && polymerType[rI] === PolymerType.NA) {
count = 0;
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
}
switch (count) {
case undefined: return DefaultColor;
case 0: return NoIssuesColor;
case 1: return OneIssueColor;
case 2: return TwoIssuesColor;
default: return ThreeOrMoreIssuesColor;
}
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};
@@ -92,6 +96,7 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
return {
factory: GeometryQualityColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
props,
contextHash,
@@ -110,6 +115,6 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color, ColorScale } from '../../../../mol-util/color';
import { StructureElement, Model } from '../../../../mol-model/structure';
import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -31,10 +31,16 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
if (rci && model) {
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const getColor = (element: ElementIndex) => {
const value = rci.get(residueIndex[element]);
return value === undefined ? DefaultColor : scale.color(value);
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const value = rci.get(residueIndex[location.element]);
return value === undefined ? DefaultColor : scale.color(value);
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};
@@ -43,6 +49,7 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
return {
factory: RandomCoilIndexColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
props,
contextHash,
@@ -61,6 +68,6 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromNmr(ctx.structure.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -19,7 +19,7 @@ import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
import Type from '../../../mol-script/language/type';
import { Type } from '../../../mol-script/language/type';
import { Asset } from '../../../mol-util/assets';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';

View File

@@ -16,7 +16,7 @@ import { RepresentationContext, RepresentationParamsGetter, Representation } fro
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
import { VisualContext } from '../../../mol-repr/visual';
import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from './prop';
@@ -28,6 +28,7 @@ import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
import { Sphere3D } from '../../../mol-math/geometry';
import { bondLabel } from '../../../mol-theme/label';
import { getUnitKindsParam } from '../../../mol-repr/structure/params';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
//

View File

@@ -9,6 +9,7 @@ import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
import { Scene } from '../mol-gl/scene';
export { ICamera, Camera };
@@ -86,7 +87,12 @@ class Camera implements ICamera {
if (changed) {
Mat4.mul(this.projectionView, this.projection, this.view);
Mat4.invert(this.inverseProjectionView, this.projectionView);
if (!Mat4.tryInvert(this.inverseProjectionView, this.projectionView)) {
Mat4.copy(this.view, this.prevView);
Mat4.copy(this.projection, this.prevProjection);
Mat4.mul(this.projectionView, this.projection, this.view);
return false;
}
Mat4.copy(this.prevView, this.view);
Mat4.copy(this.prevProjection, this.projection);
@@ -126,6 +132,23 @@ class Camera implements ICamera {
return state;
}
getInvariantFocus(target: Vec3, radius: number, up: Vec3, dir: Vec3): Partial<Camera.Snapshot> {
const r = Math.max(radius, 0.01);
const targetDistance = this.getTargetDistance(r);
Vec3.copy(this.deltaDirection, dir);
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
Vec3.sub(this.newPosition, target, this.deltaDirection);
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
state.target = Vec3.clone(target);
state.radius = r;
state.position = Vec3.clone(this.newPosition);
Vec3.copy(state.up, up);
return state;
}
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
if (radius > 0) {
this.setState(this.getFocus(target, radius, up, dir), durationMs);
@@ -150,6 +173,8 @@ class Camera implements ICamera {
namespace Camera {
export type Mode = 'perspective' | 'orthographic'
export type SnapshotProvider = Partial<Snapshot> | ((scene: Scene, camera: Camera) => Partial<Snapshot>)
/**
* Sets an offseted view in a larger frustum. This is useful for
* - multi-window or multi-monitor/multi-machine setups
@@ -209,7 +234,7 @@ namespace Camera {
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
radius: 10,
radius: 0,
radiusMax: 10,
fog: 50,
clipFar: true
@@ -247,6 +272,18 @@ namespace Camera {
return out;
}
export function areSnapshotsEqual(a: Snapshot, b: Snapshot) {
return a.mode === b.mode
&& a.fov === b.fov
&& a.radius === b.radius
&& a.radiusMax === b.radiusMax
&& a.fog === b.fog
&& a.clipFar === b.clipFar
&& Vec3.exactEquals(a.position, b.position)
&& Vec3.exactEquals(a.up, b.up)
&& Vec3.exactEquals(a.target, b.target);
}
}
function updateOrtho(camera: Camera) {
@@ -319,16 +356,17 @@ function updateClip(camera: Camera) {
let far = cameraDistance + normalizedFar;
const fogNearFactor = -(50 - fog) / 50;
let fogNear = cameraDistance - (normalizedFar * fogNearFactor);
let fogFar = far;
const fogNear = cameraDistance - (normalizedFar * fogNearFactor);
const fogFar = far;
if (mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
} else {
near = Math.max(0, near);
far = Math.max(0, far);
// not too close to 0 as it causes issues with outline rendering
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
}
if (near === far) {
@@ -337,7 +375,7 @@ function updateClip(camera: Camera) {
}
camera.near = near;
camera.far = far;
camera.far = 2 * far; // avoid precision issues distingushing far objects from background
camera.fogNear = fogNear;
camera.fogFar = fogFar;
}

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -8,14 +8,14 @@
import { BehaviorSubject, Subscription } from 'rxjs';
import { now } from '../mol-util/now';
import { Vec3, Vec2 } from '../mol-math/linear-algebra';
import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer';
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer';
import { InputObserver, ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer';
import { Renderer, RendererStats, RendererParams } from '../mol-gl/renderer';
import { GraphicsRenderObject } from '../mol-gl/render-object';
import { TrackballControls, TrackballControlsParams } from './controls/trackball';
import { Viewport } from './camera/util';
import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context';
import { Representation } from '../mol-repr/representation';
import Scene from '../mol-gl/scene';
import { Scene } from '../mol-gl/scene';
import { PickingId } from '../mol-geo/geometry/picking';
import { MarkerAction } from '../mol-util/marker-action';
import { Loci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
@@ -24,7 +24,7 @@ import { ParamDefinition as PD } from '../mol-util/param-definition';
import { DebugHelperParams } from './helper/bounding-sphere-helper';
import { SetUtils } from '../mol-util/set';
import { Canvas3dInteractionHelper } from './helper/interaction-events';
import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing';
import { PostprocessingParams } from './passes/postprocessing';
import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PickData } from './passes/pick';
import { PickHelper } from './passes/pick';
@@ -47,11 +47,11 @@ export const Canvas3DParams = {
on: PD.Group(StereoCameraParams),
off: PD.Group({})
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
manualReset: PD.Boolean(false, { isHidden: true })
manualReset: PD.Boolean(false, { isHidden: true }),
}, { pivot: 'mode' }),
cameraFog: PD.MappedStatic('on', {
on: PD.Group({
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
intensity: PD.Numeric(15, { min: 1, max: 100, step: 1 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Show fog in the distance' }),
@@ -61,11 +61,17 @@ export const Canvas3DParams = {
}, { pivot: 'radius' }),
viewport: PD.MappedStatic('canvas', {
canvas: PD.Group({}),
custom: PD.Group({
'static-frame': PD.Group({
x: PD.Numeric(0),
y: PD.Numeric(0),
width: PD.Numeric(128),
height: PD.Numeric(128)
}),
'relative-frame': PD.Group({
x: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
y: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
width: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 }),
height: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 })
})
}),
@@ -85,6 +91,110 @@ export type PartialCanvas3DProps = {
[K in keyof Canvas3DProps]?: Canvas3DProps[K] extends { name: string, params: any } ? Canvas3DProps[K] : Partial<Canvas3DProps[K]>
}
export { Canvas3DContext };
/** Can be used to create multiple Canvas3D objects */
interface Canvas3DContext {
readonly canvas: HTMLCanvasElement
readonly webgl: WebGLContext
readonly input: InputObserver
readonly passes: Passes
readonly attribs: Readonly<Canvas3DContext.Attribs>
readonly contextLost: BehaviorSubject<now.Timestamp>
readonly contextRestored: BehaviorSubject<now.Timestamp>
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
namespace Canvas3DContext {
export const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
preserveDrawingBuffer: true,
pixelScale: 1,
pickScale: 0.25,
enableWboit: true
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
const { antialias, preserveDrawingBuffer, pixelScale } = a;
const gl = getGLContext(canvas, {
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
depth: true, // the renderer requires a depth buffer
premultipliedAlpha: true, // the renderer outputs PMA
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const input = InputObserver.fromElement(canvas, { pixelScale, preventGestures: true });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
// Hold down shift+ctrl+alt and press any mouse button to call `loseContext`.
// After 1 second `restoreContext` will be called.
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
if (isDebugMode) console.log('lose context');
loseContextExt.loseContext();
setTimeout(() => {
if (!webgl.isContextLost) return;
if (isDebugMode) console.log('restore context');
loseContextExt.restoreContext();
}, 1000);
}, false);
}
}
// https://www.khronos.org/webgl/wiki/HandlingContextLost
const contextLost = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const handleWebglContextLost = (e: Event) => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
contextLost.next(now());
};
const handlewWebglContextRestored = () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored(() => {
passes.draw.reset();
});
if (isDebugMode) console.log('context restored');
};
canvas.addEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.addEventListener('webglcontextrestored', handlewWebglContextRestored, false);
return {
canvas,
webgl,
input,
passes,
attribs: a,
contextLost,
contextRestored: webgl.contextRestored,
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
input.dispose();
canvas.removeEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.removeEventListener('webglcontextrestored', handlewWebglContextRestored, false);
webgl.destroy(options);
}
};
}
}
export { Canvas3D };
interface Canvas3D {
@@ -97,7 +207,7 @@ interface Canvas3D {
*/
commit(isSynchronous?: boolean): void
/**
* Funcion for external "animation" control
* Function for external "animation" control
* Calls commit.
*/
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
@@ -110,23 +220,34 @@ interface Canvas3D {
/** Reset the timers, used by "animate" */
resetTime(t: number): void
animate(): void
pause(): void
/**
* Pause animation loop and optionally any rendering
* @param noDraw pause any rendering (drawPaused = true)
*/
pause(noDraw?: boolean): void
/** Sets drawPaused = false without starting the built in animation loop */
resume(): void
identify(x: number, y: number): PickData | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId | undefined): Representation.Loci
notifyDidDraw: boolean,
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly commited: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
readonly resized: BehaviorSubject<any>
handleResize(): void
/** performs handleResize on the next animation frame */
requestResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
requestCameraReset(options?: { durationMs?: number, snapshot?: Camera.SnapshotProvider }): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
readonly boundingSphereVisible: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
getImagePass(props: Partial<ImageProps>): ImagePass
getRenderObjects(): GraphicsRenderObject[]
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
@@ -149,58 +270,7 @@ namespace Canvas3D {
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number, enableWboit: boolean }> = {}) {
const gl = getGLContext(canvas, {
alpha: true,
antialias: (attribs.antialias ?? true) && !attribs.enableWboit,
depth: true,
preserveDrawingBuffer: true,
premultipliedAlpha: true,
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const { pixelScale } = attribs;
const input = InputObserver.fromElement(canvas, { pixelScale });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
if (isDebugMode) console.log('lose context');
loseContextExt.loseContext();
setTimeout(() => {
if (!webgl.isContextLost) return;
if (isDebugMode) console.log('restore context');
loseContextExt.restoreContext();
}, 1000);
}, false);
}
}
// https://www.khronos.org/webgl/wiki/HandlingContextLost
canvas.addEventListener('webglcontextlost', e => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
}, false);
canvas.addEventListener('webglcontextrestored', () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored();
if (isDebugMode) console.log('context restored');
}, false);
return create(webgl, input, passes, props, { pixelScale });
}
export function create(webgl: WebGLContext, input: InputObserver, passes: Passes, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ pixelScale: number }>): Canvas3D {
export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
@@ -209,6 +279,7 @@ namespace Canvas3D {
let startTime = now();
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const { gl, contextRestored } = webgl;
@@ -236,10 +307,10 @@ namespace Canvas3D {
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
let drawPending = false;
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
let resizeRequested = false;
let notifyDidDraw = true;
@@ -247,7 +318,11 @@ namespace Canvas3D {
let loci: Loci = EmptyLoci;
let repr: Representation.Any = Representation.Empty;
if (pickingId) {
const cameraHelperLoci = helper.camera.getLoci(pickingId);
if (cameraHelperLoci !== EmptyLoci) return { loci: cameraHelperLoci, repr };
loci = helper.handle.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
@@ -269,11 +344,13 @@ namespace Canvas3D {
changed = repr.mark(loci, action);
} else {
changed = helper.handle.mark(loci, action);
changed = helper.camera.mark(loci, action) || changed;
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed) {
scene.update(void 0, true);
helper.handle.scene.update(void 0, true);
helper.camera.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw(true);
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
@@ -282,6 +359,14 @@ namespace Canvas3D {
function render(force: boolean) {
if (webgl.isContextLost) return false;
let resized = false;
if (resizeRequested) {
handleResize(false);
resizeRequested = false;
resized = true;
}
if (x > gl.drawingBufferWidth || x + width < 0 ||
y > gl.drawingBufferHeight || y + height < 0
) return false;
@@ -289,9 +374,13 @@ namespace Canvas3D {
let didRender = false;
controls.update(currentTime);
const cameraChanged = camera.update();
const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
if (force || cameraChanged || multiSampleChanged) {
const shouldRender = force || cameraChanged || resized || forceNextRender;
forceNextRender = false;
const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample);
if (shouldRender || multiSampleChanged) {
let cam: Camera | StereoCamera = camera;
if (p.camera.stereo.name === 'on') {
stereoCamera.update();
@@ -301,9 +390,7 @@ namespace Canvas3D {
if (MultiSamplePass.isEnabled(p.multiSample)) {
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
} else {
const toDrawingBuffer = !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0 && !passes.draw.wboitEnabled;
passes.draw.render(renderer, cam, scene, helper, toDrawingBuffer, p.transparentBackground);
if (!toDrawingBuffer) passes.postprocessing.render(cam, true, p.postprocessing);
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing);
}
pickHelper.dirty = true;
didRender = true;
@@ -312,22 +399,20 @@ namespace Canvas3D {
return didRender;
}
let forceNextDraw = false;
let forceNextRender = false;
let forceDrawAfterAllCommited = false;
let currentTime = 0;
let drawPaused = false;
function draw(force?: boolean) {
if (render(!!force || forceNextDraw) && notifyDidDraw) {
if (drawPaused) return;
if (render(!!force) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
forceNextDraw = false;
drawPending = false;
}
function requestDraw(force?: boolean) {
if (drawPending) return;
drawPending = true;
forceNextDraw = !!force;
forceNextRender = forceNextRender || !!force;
}
let animationFrameHandle = 0;
@@ -358,11 +443,13 @@ namespace Canvas3D {
}
function animate() {
drawPaused = false;
controls.start(now());
if (animationFrameHandle === 0) _animate();
}
function pause() {
function pause(noDraw = false) {
drawPaused = noDraw;
cancelAnimationFrame(animationFrameHandle);
animationFrameHandle = 0;
}
@@ -382,17 +469,28 @@ namespace Canvas3D {
draw(true);
forceDrawAfterAllCommited = false;
}
commited.next(now());
}
}
function resolveCameraReset() {
if (!cameraResetRequested) return;
const { center, radius } = scene.boundingSphereVisible;
const boundingSphere = scene.boundingSphereVisible;
const { center, radius } = boundingSphere;
const autoAdjustControls = controls.props.autoAdjustMinMaxDistance;
if (autoAdjustControls.name === 'on') {
const minDistance = autoAdjustControls.params.minDistanceFactor * radius + autoAdjustControls.params.minDistancePadding;
const maxDistance = Math.max(autoAdjustControls.params.maxDistanceFactor * radius, autoAdjustControls.params.maxDistanceMin);
controls.setProps({ minDistance, maxDistance });
}
if (radius > 0) {
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
const focus = camera.getFocus(center, radius);
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
const next = typeof nextCameraResetSnapshot === 'function' ? nextCameraResetSnapshot(scene, camera) : nextCameraResetSnapshot;
const snapshot = next ? { ...focus, ...next } : focus;
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
}
@@ -455,8 +553,16 @@ namespace Canvas3D {
drawCount: r.values.drawCount.ref.value,
instanceCount: r.values.instanceCount.ref.value,
materialId: r.materialId,
renderItemId: r.id,
})));
console.log(webgl.stats);
const { texture, attribute, elements } = webgl.resources.getByteCounts();
console.log({
texture: `${(texture / 1024 / 1024).toFixed(3)} MiB`,
attribute: `${(attribute / 1024 / 1024).toFixed(3)} MiB`,
elements: `${(elements / 1024 / 1024).toFixed(3)} MiB`,
});
}
function add(repr: Representation.Any) {
@@ -541,10 +647,22 @@ namespace Canvas3D {
const contextRestoredSub = contextRestored.subscribe(() => {
pickHelper.dirty = true;
draw(true);
// Unclear why, but in Chrome with wboit enabled the first `draw` only clears
// the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
// context loss so it is unclear if it behaves the same.
draw(true);
});
const resized = new BehaviorSubject<any>(0);
function handleResize(draw = true) {
passes.updateSize();
updateViewport();
syncViewport();
if (draw) requestDraw(true);
resized.next(+new Date());
}
return {
webgl,
@@ -586,16 +704,14 @@ namespace Canvas3D {
animate,
resetTime,
pause,
resume: () => { drawPaused = false; },
identify,
mark,
getLoci,
handleResize: () => {
passes.updateSize();
updateViewport();
syncViewport();
requestDraw(true);
resized.next(+new Date());
handleResize,
requestResize: () => {
resizeRequested = true;
},
requestCameraReset: options => {
nextCameraResetDuration = options?.durationMs;
@@ -604,9 +720,11 @@ namespace Canvas3D {
},
camera,
boundingSphere: scene.boundingSphere,
boundingSphereVisible: scene.boundingSphereVisible,
get notifyDidDraw() { return notifyDidDraw; },
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
commited,
reprCount,
resized,
setProps: (properties, doNotRequestDraw = false) => {
@@ -670,6 +788,11 @@ namespace Canvas3D {
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
},
getRenderObjects(): GraphicsRenderObject[] {
const renderObjects: GraphicsRenderObject[] = [];
scene.forEach((_, ro) => renderObjects.push(ro));
return renderObjects;
},
get props() {
return getProps();
@@ -688,7 +811,6 @@ namespace Canvas3D {
scene.clear();
helper.debug.clear();
input.dispose();
controls.dispose();
renderer.dispose();
interactionHelper.dispose();
@@ -696,16 +818,30 @@ namespace Canvas3D {
};
function updateViewport() {
const oldX = x, oldY = y, oldWidth = width, oldHeight = height;
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
} else {
} else if (p.viewport.name === 'static-frame') {
x = p.viewport.params.x * webgl.pixelRatio;
y = p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
height = p.viewport.params.height * webgl.pixelRatio;
y = gl.drawingBufferHeight - height - p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
} else if (p.viewport.name === 'relative-frame') {
x = Math.round(p.viewport.params.x * gl.drawingBufferWidth);
height = Math.round(p.viewport.params.height * gl.drawingBufferHeight);
y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight);
width = Math.round(p.viewport.params.width * gl.drawingBufferWidth);
// if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x;
// if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1;
// console.log({ x, y, width, height });
}
if (oldX !== x || oldY !== y || oldWidth !== width || oldHeight !== height) {
forceNextRender = true;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -10,7 +10,7 @@
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { Viewport } from '../camera/util';
import InputObserver, { DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
@@ -36,8 +36,8 @@ export const DefaultTrackballBindings = {
export const TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
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 }),
rotateSpeed: PD.Numeric(5.0, { min: 1, max: 10, step: 1 }),
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
@@ -49,7 +49,24 @@ export const TrackballControlsParams = {
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
gestureScaleFactor: PD.Numeric(1, {}, { isHidden: true }),
maxWheelDelta: PD.Numeric(0.02, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
/**
* minDistance = minDistanceFactor * boundingSphere.radius + minDistancePadding
* maxDistance = max(maxDistanceFactor * boundingSphere.radius, maxDistanceMin)
*/
autoAdjustMinMaxDistance: PD.MappedStatic('on', {
off: PD.EmptyGroup(),
on: PD.Group({
minDistanceFactor: PD.Numeric(0),
minDistancePadding: PD.Numeric(5),
maxDistanceFactor: PD.Numeric(10),
maxDistanceMin: PD.Numeric(20)
})
}, { isHidden: true })
};
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
@@ -77,6 +94,7 @@ namespace TrackballControls {
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
const wheelSub = input.wheel.subscribe(onWheel);
const pinchSub = input.pinch.subscribe(onPinch);
const gestureSub = input.gesture.subscribe(onGesture);
let _isInteracting = false;
@@ -138,7 +156,8 @@ namespace TrackballControls {
const dy = _rotCurr[1] - _rotPrev[1];
Vec3.set(rotMoveDir, dx, dy, 0);
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio;
const aspectRatio = input.width / input.height;
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
if (angle) {
Vec3.sub(_eye, camera.position, camera.target);
@@ -375,25 +394,33 @@ namespace TrackballControls {
_isInteracting = false;
}
function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
function onWheel({ x, y, spinX, spinY, dz, buttons, modifiers }: WheelInput) {
if (outsideViewport(x, y)) return;
const delta = absMax(dx, dy, dz);
let delta = absMax(spinX * 0.075, spinY * 0.075, dz * 0.0001);
if (delta < -p.maxWheelDelta) delta = -p.maxWheelDelta;
else if (delta > p.maxWheelDelta) delta = p.maxWheelDelta;
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001;
_zoomEnd[1] += delta;
}
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
_focusEnd[1] += delta * 0.0001;
_focusEnd[1] += delta;
}
}
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
function onPinch({ fractionDelta, buttons, modifiers }: PinchInput) {
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_isInteracting = true;
_zoomEnd[1] += (fraction - 1) * 0.1;
_zoomEnd[1] += p.gestureScaleFactor * fractionDelta;
}
}
function onGesture({ deltaScale }: GestureInput) {
_isInteracting = true;
_zoomEnd[1] += p.gestureScaleFactor * deltaScale;
}
function dispose() {
if (disposed) return;
disposed = true;
@@ -401,6 +428,7 @@ namespace TrackballControls {
dragSub.unsubscribe();
wheelSub.unsubscribe();
pinchSub.unsubscribe();
gestureSub.unsubscribe();
interactionEndSub.unsubscribe();
}

View File

@@ -9,7 +9,7 @@ import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import Scene from '../../mol-gl/scene';
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';
@@ -160,5 +160,5 @@ const instanceMaterialId = getNextMaterialId();
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false }, materialId);
return createRenderObject('mesh', values, { disposed: false, visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
}

View File

@@ -1,30 +1,35 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import Scene from '../../mol-gl/scene';
import { Camera, ICamera } from '../camera';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ColorNames } from '../../mol-util/color/names';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Viewport } from '../camera/util';
import { Sphere3D } from '../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import produce from 'immer';
import { Interval } from '../../mol-data/int/interval';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { PickingId } from '../../mol-geo/geometry/picking';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Sphere3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
import { Shape } from '../../mol-model/shape';
import { Visual } from '../../mol-repr/visual';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera, ICamera } from '../camera';
import { Viewport } from '../camera/util';
// TODO add scale line/grid
const AxesParams = {
...Mesh.Params,
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
colorX: PD.Color(ColorNames.red, { isEssential: true }),
colorY: PD.Color(ColorNames.green, { isEssential: true }),
@@ -70,6 +75,7 @@ export class CameraHelper {
this.scene.clear();
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
this.renderObject = createAxesRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();
@@ -86,6 +92,32 @@ export class CameraHelper {
return this.props.axes.name === 'on';
}
getLoci(pickingId: PickingId) {
const { objectId, groupId, instanceId } = pickingId;
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
return CameraAxesLoci(this, groupId, instanceId);
}
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
if (!this.renderObject) return false;
if (!isCameraAxesLoci(loci)) return false;
let changed = false;
const groupCount = this.renderObject.values.uGroupCount.ref.value;
const { elements } = loci;
for (const { groupId, instanceId } of elements) {
const idx = instanceId * groupCount + groupId;
if (apply(Interval.ofSingleton(idx))) changed = true;
}
return changed;
}
mark(loci: Loci, action: MarkerAction) {
if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
if (!isCameraAxesLoci(loci)) return false;
if (loci.data !== this) return false;
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
}
update(camera: ICamera) {
if (!this.renderObject) return;
@@ -101,6 +133,38 @@ export class CameraHelper {
}
}
export const enum CameraHelperAxis {
None = 0,
X,
Y,
Z,
XY,
XZ,
YZ
}
function getAxisLabel(axis: number) {
switch (axis) {
case CameraHelperAxis.X: return 'X Axis';
case CameraHelperAxis.Y: return 'Y Axis';
case CameraHelperAxis.Z: return 'Z Axis';
case CameraHelperAxis.XY: return 'XY Plane';
case CameraHelperAxis.XZ: return 'XZ Plane';
case CameraHelperAxis.YZ: return 'YZ Plane';
default: return 'Axes';
}
}
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
void 0 /** bounding sphere */,
() => getAxisLabel(groupId));
}
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
return x.kind === 'data-loci' && x.tag === 'camera-axes';
}
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
const { near, far } = camera;
@@ -133,27 +197,52 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
function createAxesMesh(scale: number, mesh?: Mesh) {
const state = MeshBuilder.createState(512, 256, mesh);
const radius = 0.05 * scale;
const radius = 0.075 * scale;
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
state.currentGroup = 0;
state.currentGroup = CameraHelperAxis.None;
addSphere(state, Vec3.origin, radius, 2);
state.currentGroup = 1;
state.currentGroup = CameraHelperAxis.X;
addSphere(state, x, radius, 2);
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
state.currentGroup = 2;
state.currentGroup = CameraHelperAxis.Y;
addSphere(state, y, radius, 2);
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
state.currentGroup = 3;
state.currentGroup = CameraHelperAxis.Z;
addSphere(state, z, radius, 2);
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
Vec3.scale(x, x, 0.5);
Vec3.scale(y, y, 0.5);
Vec3.scale(z, z, 0.5);
state.currentGroup = CameraHelperAxis.XY;
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
const xy = Vec3.add(Vec3(), x, y);
MeshBuilder.addTriangle(state, xy, x, y);
MeshBuilder.addTriangle(state, xy, y, x);
state.currentGroup = CameraHelperAxis.XZ;
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
const xz = Vec3.add(Vec3(), x, z);
MeshBuilder.addTriangle(state, xz, x, z);
MeshBuilder.addTriangle(state, xz, z, x);
state.currentGroup = CameraHelperAxis.YZ;
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
const yz = Vec3.add(Vec3(), y, z);
MeshBuilder.addTriangle(state, yz, y, z);
MeshBuilder.addTriangle(state, yz, z, y);
return MeshBuilder.getMesh(state);
}

View File

@@ -5,7 +5,7 @@
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import Scene from '../../mol-gl/scene';
import { Scene } from '../../mol-gl/scene';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
@@ -72,6 +72,7 @@ export class HandleHelper {
this.scene.clear();
const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
this.renderObject = createHandleRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Scene from '../../mol-gl/scene';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { BoundingSphereHelper, DebugHelperParams } from './bounding-sphere-helper';

View File

@@ -7,7 +7,7 @@
import { PickingId } from '../../mol-geo/geometry/picking';
import { Representation } from '../../mol-repr/representation';
import InputObserver, { ModifiersKeys, ButtonsType } from '../../mol-util/input/input-observer';
import { InputObserver, ModifiersKeys, ButtonsType } from '../../mol-util/input/input-observer';
import { RxEventHelper } from '../../mol-util/rx-event-helper';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Camera } from '../camera';

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -7,8 +7,8 @@
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createNullRenderTarget, RenderTarget } from '../../mol-gl/webgl/render-target';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { Texture } from '../../mol-gl/webgl/texture';
import { Camera, ICamera } from '../camera';
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
@@ -20,10 +20,12 @@ import { ValueCell } from '../../mol-util';
import { Vec2 } from '../../mol-math/linear-algebra';
import { Helper } from '../helper/helper';
import quad_vert from '../../mol-gl/shader/quad.vert';
import depthMerge_frag from '../../mol-gl/shader/depth-merge.frag';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { depthMerge_frag } from '../../mol-gl/shader/depth-merge.frag';
import { copy_frag } from '../../mol-gl/shader/copy.frag';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
const DepthMergeSchema = {
...QuadSchema,
@@ -50,6 +52,27 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
return createComputeRenderable(renderItem, values);
}
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
function getCopyRenderable(ctx: WebGLContext, colorTexture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export class DrawPass {
private readonly drawTarget: RenderTarget
@@ -57,21 +80,27 @@ export class DrawPass {
readonly depthTexture: Texture
readonly depthTexturePrimitives: Texture
private readonly packedDepth: boolean
readonly packedDepth: boolean
private depthTarget: RenderTarget
private depthTargetPrimitives: RenderTarget | null
private depthTargetVolumes: RenderTarget | null
private depthTextureVolumes: Texture
private depthMerge: DepthMergeRenderable
private copyFboTarget: CopyRenderable
private copyFboPostprocessing: CopyRenderable
private wboit: WboitPass | undefined
readonly postprocessing: PostprocessingPass
private readonly antialiasing: AntialiasingPass
get wboitEnabled() {
return !!this.wboit?.enabled;
return !!this.wboit?.supported;
}
constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) {
const { extensions, resources } = webgl;
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
@@ -84,8 +113,8 @@ export class DrawPass {
this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
if (!this.packedDepth) {
this.depthTexturePrimitives.define(width, height);
this.depthTextureVolumes.define(width, height);
@@ -93,6 +122,15 @@ export class DrawPass {
this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth);
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
this.postprocessing = new PostprocessingPass(webgl, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.copyFboTarget = getCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = getCopyRenderable(webgl, this.postprocessing.target.texture);
}
reset() {
this.wboit?.reset();
}
setSize(width: number, height: number) {
@@ -117,9 +155,15 @@ export class DrawPass {
ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height));
if (this.wboit?.enabled) {
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
if (this.wboit?.supported) {
this.wboit.setSize(width, height);
}
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
}
}
@@ -137,41 +181,50 @@ export class DrawPass {
this.depthMerge.render();
}
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) {
if (!this.wboit?.enabled) throw new Error('expected wboit to be enabled');
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
const renderTarget = toDrawingBuffer ? this.drawTarget : this.colorTarget;
renderTarget.bind();
this.colorTarget.bind();
renderer.clear(true);
// render opaque primitives
this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth');
renderTarget.bind();
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
renderer.clearDepth();
renderer.renderWboitOpaque(scene.primitives, camera, null);
// render opaque volumes
this.depthTextureVolumes.attachFramebuffer(renderTarget.framebuffer, 'depth');
renderTarget.bind();
this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
renderer.clearDepth();
renderer.renderWboitOpaque(scene.volumes, camera, this.depthTexturePrimitives);
// merge depth of opaque primitives and volumes
this._depthMerge();
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
}
// render transparent primitives and volumes
this.wboit.bind();
renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexture);
renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexture);
// evaluate wboit
this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth');
renderTarget.bind();
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
this.postprocessing.target.bind();
} else {
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
}
this.wboit.render();
}
private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) {
private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
this.drawTarget.bind();
} else {
this.colorTarget.bind();
if (!this.packedDepth) {
@@ -182,22 +235,23 @@ export class DrawPass {
renderer.clear(true);
renderer.renderBlendedOpaque(scene.primitives, camera, null);
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (!toDrawingBuffer && this.depthTargetPrimitives) {
this.depthTargetPrimitives.bind();
renderer.clear(false);
renderer.renderDepth(scene.primitives, camera, null);
this.colorTarget.bind();
}
// do direct-volume rendering
if (!toDrawingBuffer) {
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetPrimitives) {
this.depthTargetPrimitives.bind();
renderer.clear(false);
// TODO: this should only render opaque
renderer.renderDepth(scene.primitives, camera, null);
this.colorTarget.bind();
}
// do direct-volume rendering
if (!this.packedDepth) {
this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
renderer.clearDepth(); // from previous frame
}
renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives);
renderer.renderBlendedVolumeOpaque(scene.volumes, camera, this.depthTexturePrimitives);
// do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetVolumes) {
@@ -207,29 +261,52 @@ export class DrawPass {
this.colorTarget.bind();
}
if (!this.packedDepth) {
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
// merge depths from primitive and volume rendering
this._depthMerge();
this.colorTarget.bind();
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
}
renderer.renderBlendedVolumeTransparent(scene.volumes, camera, this.depthTexturePrimitives);
const target = PostprocessingPass.isEnabled(postprocessingProps)
? this.postprocessing.target : this.colorTarget;
if (!this.packedDepth) {
this.depthTexturePrimitives.attachFramebuffer(target.framebuffer, 'depth');
}
target.bind();
}
renderer.renderBlendedTransparent(scene.primitives, camera, null);
// merge depths from primitive and volume rendering
if (!toDrawingBuffer) {
this._depthMerge();
this.colorTarget.bind();
}
}
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean) {
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
const volumeRendering = scene.volumes.renderables.length > 0;
const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
const { x, y, width, height } = camera.viewport;
renderer.setViewport(x, y, width, height);
renderer.update(camera);
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
this.drawTarget.bind();
renderer.clear(false);
}
if (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, toDrawingBuffer);
this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
} else {
this._renderBlended(renderer, camera, scene, toDrawingBuffer);
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
}
if (postprocessingEnabled) {
this.postprocessing.target.bind();
} else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) {
this.colorTarget.bind();
} else {
this.drawTarget.bind();
}
if (helper.debug.isEnabled) {
@@ -245,18 +322,40 @@ export class DrawPass {
renderer.renderBlended(helper.camera.scene, helper.camera.camera, null);
}
if (antialiasingEnabled) {
this.antialiasing.render(camera, toDrawingBuffer, postprocessingProps);
} else if (toDrawingBuffer) {
this.drawTarget.bind();
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
if (postprocessingEnabled) {
this.copyFboPostprocessing.render();
} else if (volumeRendering || this.wboitEnabled) {
this.copyFboTarget.render();
}
}
this.webgl.gl.flush();
}
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) {
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
renderer.setTransparentBackground(transparentBackground);
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
if (StereoCamera.is(camera)) {
this._render(renderer, camera.left, scene, helper, toDrawingBuffer);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer);
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer);
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
}
}
getColorTarget(postprocessingProps: PostprocessingProps): RenderTarget {
if (AntialiasingPass.isEnabled(postprocessingProps)) {
return this.antialiasing.target;
} else if (PostprocessingPass.isEnabled(postprocessingProps)) {
return this.postprocessing.target;
}
return this.colorTarget;
}
}

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { TextureSpec, UniformSpec, DefineSpec, Values } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture } from '../../mol-gl/webgl/texture';
import { Vec2 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { fxaa_frag } from '../../mol-gl/shader/fxaa.frag';
import { Viewport } from '../camera/util';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
export const FxaaParams = {
edgeThresholdMin: PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }),
edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }),
iterations: PD.Numeric(12, { min: 0, max: 16, step: 1 }, { description: 'Number of edge exploration steps.' }),
subpixelQuality: PD.Numeric(0.30, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }),
};
export type FxaaProps = PD.Values<typeof FxaaParams>
export class FxaaPass {
private readonly renderable: FxaaRenderable
constructor(private webgl: WebGLContext, input: Texture) {
this.renderable = getFxaaRenderable(webgl, input);
}
private updateState(viewport: Viewport) {
const { gl, state } = this.webgl;
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
setSize(width: number, height: number) {
ValueCell.update(this.renderable.values.uTexSizeInv, Vec2.set(this.renderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
}
update(input: Texture, props: FxaaProps) {
const { values } = this.renderable;
const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props;
let needsUpdate = false;
if (values.tColor.ref.value !== input) {
ValueCell.update(this.renderable.values.tColor, input);
needsUpdate = true;
}
if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin);
if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax);
if (values.dIterations.ref.value !== iterations) needsUpdate = true;
ValueCell.updateIfChanged(values.dIterations, iterations);
if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true;
ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality);
if (needsUpdate) {
this.renderable.update();
}
}
render(viewport: Viewport, target: RenderTarget | undefined) {
if (target) {
target.bind();
} else {
this.webgl.unbindFramebuffer();
}
this.updateState(viewport);
this.renderable.render();
}
}
//
const FxaaSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
dEdgeThresholdMin: DefineSpec('number'),
dEdgeThresholdMax: DefineSpec('number'),
dIterations: DefineSpec('number'),
dSubpixelQuality: DefineSpec('number'),
};
const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag);
type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>>
function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof FxaaSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
dEdgeThresholdMin: ValueCell.create(0.0312),
dEdgeThresholdMax: ValueCell.create(0.125),
dIterations: ValueCell.create(12),
dSubpixelQuality: ValueCell.create(0.3),
};
const schema = { ...FxaaSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

View File

@@ -1,16 +1,16 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
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 { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DrawPass } from './draw';
import { PostprocessingPass, PostprocessingParams } from './postprocessing';
import { PostprocessingParams } from './postprocessing';
import { MultiSamplePass, MultiSampleParams, MultiSampleHelper } from './multi-sample';
import { Camera } from '../camera';
import { Viewport } from '../camera/util';
@@ -38,7 +38,6 @@ export class ImagePass {
get colorTarget() { return this._colorTarget; }
private readonly drawPass: DrawPass
private readonly postprocessingPass: PostprocessingPass
private readonly multiSamplePass: MultiSamplePass
private readonly multiSampleHelper: MultiSampleHelper
private readonly helper: Helper
@@ -50,8 +49,7 @@ export class ImagePass {
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
this.drawPass = new DrawPass(webgl, 128, 128, enableWboit);
this.postprocessingPass = new PostprocessingPass(webgl, this.drawPass);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass, this.postprocessingPass);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
this.helper = {
@@ -70,7 +68,6 @@ export class ImagePass {
this._height = height;
this.drawPass.setSize(width, height);
this.postprocessingPass.syncSize();
this.multiSamplePass.syncSize();
}
@@ -88,13 +85,8 @@ export class ImagePass {
this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
this._colorTarget = this.multiSamplePass.colorTarget;
} else {
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground);
if (PostprocessingPass.isEnabled(this.props.postprocessing)) {
this.postprocessingPass.render(this._camera, false, this.props.postprocessing);
this._colorTarget = this.postprocessingPass.target;
} else {
this._colorTarget = this.drawPass.colorTarget;
}
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing);
this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
}
}
@@ -111,7 +103,9 @@ export class ImagePass {
} else {
this.webgl.readPixels(viewport.x, height - viewport.y - viewport.height, w, h, array);
}
PixelData.flipY({ array, width: w, height: h });
const pixelData = PixelData.create(array, w, h);
PixelData.flipY(pixelData);
PixelData.divideByAlpha(pixelData);
return new ImageData(new Uint8ClampedArray(array), w, h);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -16,15 +16,15 @@ import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/rendera
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { Camera } from '../../mol-canvas3d/camera';
import { PostprocessingPass, PostprocessingProps } from './postprocessing';
import { PostprocessingProps } from './postprocessing';
import { DrawPass } from './draw';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { Helper } from '../helper/helper';
import { StereoCamera } from '../camera/stereo';
import quad_vert from '../../mol-gl/shader/quad.vert';
import compose_frag from '../../mol-gl/shader/compose.frag';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { compose_frag } from '../../mol-gl/shader/compose.frag';
const ComposeSchema = {
...QuadSchema,
@@ -68,12 +68,14 @@ export class MultiSamplePass {
private holdTarget: RenderTarget
private compose: ComposeRenderable
constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) {
const { colorBufferFloat, textureFloat } = webgl.extensions;
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
const { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } = webgl.extensions;
const width = drawPass.colorTarget.getWidth();
const height = drawPass.colorTarget.getHeight();
this.colorTarget = webgl.createRenderTarget(width, height, false);
this.composeTarget = webgl.createRenderTarget(width, height, false, colorBufferFloat && textureFloat ? 'float32' : 'uint8');
const type = colorBufferHalfFloat && textureHalfFloat ? 'fp16' :
colorBufferFloat && textureFloat ? 'float32' : 'uint8';
this.composeTarget = webgl.createRenderTarget(width, height, false, type);
this.holdTarget = webgl.createRenderTarget(width, height, false);
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
}
@@ -109,7 +111,7 @@ export class MultiSamplePass {
}
private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const { compose, composeTarget, drawPass, postprocessing, webgl } = this;
const { compose, composeTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
// based on the Multisample Anti-Aliasing Render Pass
@@ -123,10 +125,8 @@ export class MultiSamplePass {
const baseSampleWeight = 1.0 / offsetList.length;
const roundingRange = 1 / 32;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
// render the scene multiple times, each slightly jitter offset
@@ -143,9 +143,8 @@ export class MultiSamplePass {
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution;
ValueCell.update(compose.values.uWeight, sampleWeight);
// render scene and optionally postprocess
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
// compose rendered scene with compose target
composeTarget.bind();
@@ -179,7 +178,7 @@ export class MultiSamplePass {
}
private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const { compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this;
const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
// based on the Multisample Anti-Aliasing Render Pass
@@ -193,13 +192,11 @@ export class MultiSamplePass {
const { x, y, width, height } = camera.viewport;
const sampleWeight = 1.0 / offsetList.length;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing) || props.postprocessing.antialiasing.name === 'on';
if (sampleIndex === -1) {
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
ValueCell.update(compose.values.uWeight, 1.0);
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
holdTarget.bind();
@@ -212,7 +209,7 @@ export class MultiSamplePass {
sampleIndex += 1;
} else {
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
ValueCell.update(compose.values.uWeight, sampleWeight);
compose.update();
@@ -224,9 +221,8 @@ export class MultiSamplePass {
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
camera.update();
// render scene and optionally postprocess
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
// compose rendered scene with compose target
composeTarget.bind();

View File

@@ -6,29 +6,25 @@
import { DrawPass } from './draw';
import { PickPass } from './pick';
import { PostprocessingPass } from './postprocessing';
import { MultiSamplePass } from './multi-sample';
import { WebGLContext } from '../../mol-gl/webgl/context';
export class Passes {
readonly draw: DrawPass
readonly pick: PickPass
readonly postprocessing: PostprocessingPass
readonly multiSample: MultiSamplePass
constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
const { gl } = webgl;
this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
this.postprocessing = new PostprocessingPass(webgl, this.draw);
this.multiSample = new MultiSamplePass(webgl, this.draw, this.postprocessing);
this.multiSample = new MultiSamplePass(webgl, this.draw);
}
updateSize() {
const { gl } = this.webgl;
this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
this.pick.syncSize();
this.postprocessing.syncSize();
this.multiSample.syncSize();
}
}

View File

@@ -5,8 +5,8 @@
*/
import { PickingId } from '../../mol-geo/geometry/picking';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
@@ -66,14 +66,20 @@ export class PickPass {
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
const depth = this.drawPass.depthTexturePrimitives;
renderer.clear(false);
renderer.update(camera);
renderer.renderPick(scene.primitives, camera, variant, null);
renderer.renderPick(scene.volumes, camera, variant, depth);
renderer.renderPick(helper.handle.scene, camera, variant, null);
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null);
}
}
render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
renderer.update(camera);
this.objectPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'pickObject');
@@ -122,8 +128,8 @@ export class PickHelper {
this.pickX = Math.ceil(x * this.pickScale);
this.pickY = Math.ceil(y * this.pickScale);
const pickWidth = Math.ceil(width * this.pickScale);
const pickHeight = Math.ceil(height * this.pickScale);
const pickWidth = Math.floor(width * this.pickScale);
const pickHeight = Math.floor(height * this.pickScale);
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
this.pickWidth = pickWidth;

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
@@ -12,20 +13,178 @@ import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { DrawPass } from './draw';
import { Camera, ICamera } from '../../mol-canvas3d/camera';
import quad_vert from '../../mol-gl/shader/quad.vert';
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag';
import fxaa_frag from '../../mol-gl/shader/fxaa.frag';
import { StereoCamera } from '../camera/stereo';
import { ICamera } from '../../mol-canvas3d/camera';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { outlines_frag } from '../../mol-gl/shader/outlines.frag';
import { ssao_frag } from '../../mol-gl/shader/ssao.frag';
import { ssaoBlur_frag } from '../../mol-gl/shader/ssao-blur.frag';
import { postprocessing_frag } from '../../mol-gl/shader/postprocessing.frag';
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { Color } from '../../mol-util/color';
import { FxaaParams, FxaaPass } from './fxaa';
import { SmaaParams, SmaaPass } from './smaa';
const OutlinesSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
dOrthographic: DefineSpec('number'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uMaxPossibleViewZDiff: UniformSpec('f'),
};
type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
function getOutlinesRenderable(ctx: WebGLContext, depthTexture: Texture): OutlinesRenderable {
const values: Values<typeof OutlinesSchema> = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uTexSize: ValueCell.create(Vec2.create(depthTexture.getWidth(), depthTexture.getHeight())),
dOrthographic: ValueCell.create(0),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
};
const schema = { ...OutlinesSchema };
const shaderCode = ShaderCode('outlines', quad_vert, outlines_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uSamples: UniformSpec('v3[]'),
dNSamples: DefineSpec('number'),
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
uTexSize: UniformSpec('v2'),
uRadius: UniformSpec('f'),
uBias: UniformSpec('f'),
};
type SsaoRenderable = ComputeRenderable<Values<typeof SsaoSchema>>
function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRenderable {
const values: Values<typeof SsaoSchema> = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uSamples: ValueCell.create([0.0, 0.0, 1.0]),
dNSamples: ValueCell.create(1),
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
uRadius: ValueCell.create(8.0),
uBias: ValueCell.create(0.025),
};
const schema = { ...SsaoSchema };
const shaderCode = ShaderCode('ssao', quad_vert, ssao_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoBlurSchema = {
...QuadSchema,
tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uKernel: UniformSpec('f[]'),
dOcclusionKernelSize: DefineSpec('number'),
uBlurDirectionX: UniformSpec('f'),
uBlurDirectionY: UniformSpec('f'),
uMaxPossibleViewZDiff: UniformSpec('f'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
};
type SsaoBlurRenderable = ComputeRenderable<Values<typeof SsaoBlurSchema>>
function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, direction: 'horizontal' | 'vertical'): SsaoBlurRenderable {
const values: Values<typeof SsaoBlurSchema> = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())),
uKernel: ValueCell.create([0.0]),
dOcclusionKernelSize: ValueCell.create(1),
uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
uNear: ValueCell.create(0.0),
uFar: ValueCell.create(10000.0),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
};
const schema = { ...SsaoBlurSchema };
const shaderCode = ShaderCode('ssao_blur', quad_vert, ssaoBlur_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function getBlurKernel(kernelSize: number): number[] {
let sigma = kernelSize / 3.0;
let halfKernelSize = Math.floor((kernelSize + 1) / 2);
let kernel = [];
for (let x = 0; x < halfKernelSize; x++) {
kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma)));
}
return kernel;
}
function getSamples(vectorSamples: Vec3[], nSamples: number): number[] {
let samples = [];
for (let i = 0; i < nSamples; i++) {
let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
scale = 0.1 + scale * (1.0 - 0.1);
samples.push(vectorSamples[i][0] * scale);
samples.push(vectorSamples[i][1] * scale);
samples.push(vectorSamples[i][2] * scale);
}
return samples;
}
const PostprocessingSchema = {
...QuadSchema,
tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tPackedDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
dOrthographic: DefineSpec('number'),
@@ -34,28 +193,26 @@ const PostprocessingSchema = {
uFogNear: UniformSpec('f'),
uFogFar: UniformSpec('f'),
uFogColor: UniformSpec('v3'),
uTransparentBackground: UniformSpec('b'),
uMaxPossibleViewZDiff: UniformSpec('f'),
dOcclusionEnable: DefineSpec('boolean'),
dOcclusionKernelSize: DefineSpec('number'),
uOcclusionBias: UniformSpec('f'),
uOcclusionRadius: UniformSpec('f'),
dOutlineEnable: DefineSpec('boolean'),
uOutlineScale: UniformSpec('f'),
dOutlineScale: DefineSpec('number'),
uOutlineThreshold: UniformSpec('f'),
};
const PostprocessingShaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture): PostprocessingRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
const values: Values<typeof PostprocessingSchema> = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
tColor: ValueCell.create(colorTexture),
tPackedDepth: ValueCell.create(depthTexture),
uTexSize: ValueCell.create(Vec2.create(width, height)),
tDepth: ValueCell.create(depthTexture),
tOutlines: ValueCell.create(outlinesTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
dOrthographic: ValueCell.create(0),
uNear: ValueCell.create(1),
@@ -63,48 +220,46 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
uFogNear: ValueCell.create(10000),
uFogFar: ValueCell.create(10000),
uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparentBackground: ValueCell.create(false),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
dOcclusionEnable: ValueCell.create(false),
dOcclusionKernelSize: ValueCell.create(4),
uOcclusionBias: ValueCell.create(0.5),
uOcclusionRadius: ValueCell.create(64),
dOutlineEnable: ValueCell.create(false),
uOutlineScale: ValueCell.create(1 * ctx.pixelRatio),
uOutlineThreshold: ValueCell.create(0.8),
dOutlineScale: ValueCell.create(1),
uOutlineThreshold: ValueCell.create(0.33),
};
const schema = { ...PostprocessingSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', PostprocessingShaderCode, schema, values);
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export const PostprocessingParams = {
occlusion: PD.MappedStatic('off', {
occlusion: PD.MappedStatic('on', {
on: PD.Group({
kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
samples: PD.Numeric(32, {min: 1, max: 256, step: 1}),
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
outline: PD.MappedStatic('off', {
on: PD.Group({
scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
threshold: PD.Numeric(0.8, { min: 0, max: 5, step: 0.01 }),
scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Draw outline around 3D objects' }),
antialiasing: PD.MappedStatic('on', {
on: PD.Group({
edgeThresholdMin:PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }),
edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }),
iterations: PD.Numeric(12, { min: 0, max: 32, step: 1 }, { description: 'Number of edge exploration steps.' }),
subpixelQuality: PD.Numeric(1.00, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }),
}),
antialiasing: PD.MappedStatic('smaa', {
fxaa: PD.Group(FxaaParams),
smaa: PD.Group(SmaaParams),
off: PD.Group({})
}, { cycle: true, description: 'Fast Approximate Anti-Aliasing (FXAA)' }),
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
@@ -115,38 +270,216 @@ export class PostprocessingPass {
readonly target: RenderTarget
private readonly tmpTarget: RenderTarget
private readonly renderable: PostprocessingRenderable
private readonly fxaa: FxaaRenderable
private readonly outlinesTarget: RenderTarget
private readonly outlinesRenderable: OutlinesRenderable
private readonly randomHemisphereVector: Vec3[]
private readonly ssaoFramebuffer: Framebuffer
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer
private readonly ssaoDepthTexture: Texture
private readonly ssaoDepthBlurProxyTexture: Texture
private readonly ssaoRenderable: SsaoRenderable
private readonly ssaoBlurFirstPassRenderable: SsaoBlurRenderable
private readonly ssaoBlurSecondPassRenderable: SsaoBlurRenderable
private nSamples: number
private blurKernelSize: number
private readonly renderable: PostprocessingRenderable
private ssaoScale: number
private calcSsaoScale() {
// downscale ssao for high pixel-ratios
return Math.min(1, 1 / this.webgl.pixelRatio);
}
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
this.ssaoScale = this.calcSsaoScale();
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
const { colorTarget, depthTexture } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
this.target = webgl.createRenderTarget(width, height, false);
this.tmpTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture);
this.fxaa = getFxaaRenderable(webgl, this.tmpTarget.texture);
this.nSamples = 1;
this.blurKernelSize = 1;
// needs to be linear for anti-aliasing pass
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
this.randomHemisphereVector = [];
for (let i = 0; i < 256; i++) {
let v = Vec3();
v[0] = Math.random() * 2.0 - 1.0;
v[1] = Math.random() * 2.0 - 1.0;
v[2] = Math.random();
Vec3.normalize(v, v);
Vec3.scale(v, v, Math.random());
this.randomHemisphereVector.push(v);
}
this.ssaoFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthBlurProxyTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
}
syncSize() {
const width = this.drawPass.colorTarget.getWidth();
const height = this.drawPass.colorTarget.getHeight();
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
const ssaoScale = this.calcSsaoScale();
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
this.ssaoScale = ssaoScale;
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.target.setSize(width, height);
this.tmpTarget.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.define(sw, sh);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.fxaa.values.uTexSizeInv, Vec2.set(this.fxaa.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
}
}
private updateState(camera: ICamera) {
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
let needsUpdateMain = false;
let needsUpdateSsao = false;
let needsUpdateSsaoBlur = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
const outlinesEnabled = props.outline.name === 'on';
const occlusionEnabled = props.occlusion.name === 'on';
let invProjection = Mat4.identity();
Mat4.invert(invProjection, camera.projection);
if (props.occlusion.name === 'on') {
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const b = this.ssaoRenderable.values.uBounds;
const v = camera.viewport;
const s = this.ssaoScale;
Vec4.set(b.ref.value,
Math.floor(v.x * s) / (w * s),
Math.floor(v.y * s) / (h * s),
Math.ceil((v.x + v.width) * s) / (w * s),
Math.ceil((v.y + v.height) * s) / (h * s)
);
ValueCell.update(b, b.ref.value);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
needsUpdateSsaoBlur = true;
}
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
if (this.nSamples !== props.occlusion.params.samples) {
needsUpdateSsao = true;
this.nSamples = props.occlusion.params.samples;
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
}
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias);
if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) {
needsUpdateSsaoBlur = true;
this.blurKernelSize = props.occlusion.params.blurKernelSize;
const kernel = getBlurKernel(this.blurKernelSize);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
}
if (props.outline.name === 'on') {
let { threshold } = props.outline.params;
// orthographic needs lower threshold
if (camera.state.mode === 'orthographic') threshold /= 5;
const factor = Math.pow(1000, threshold) / 1000;
// use radiusMax for stable outlines when zooming
const maxPossibleViewZDiff = factor * camera.state.radiusMax;
const outlineScale = props.outline.params.scale - 1;
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
}
ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.renderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.renderable.values.uFogFar, camera.fogFar);
ValueCell.updateIfChanged(this.renderable.values.uFogNear, camera.fogNear);
ValueCell.update(this.renderable.values.uFogColor, Color.toVec3Normalized(this.renderable.values.uFogColor.ref.value, backgroundColor));
ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic);
if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled);
if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
if (needsUpdateSsao) {
this.ssaoRenderable.update();
}
if (needsUpdateSsaoBlur) {
this.ssaoBlurFirstPassRenderable.update();
this.ssaoBlurSecondPassRenderable.update();
}
if (needsUpdateMain) {
this.renderable.update();
}
const { gl, state } = this.webgl;
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
@@ -154,86 +487,36 @@ export class PostprocessingPass {
const { x, y, width, height } = camera.viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
private _renderPostprocessing(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
const { values } = this.renderable;
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
this.updateState(camera, transparentBackground, backgroundColor, props);
ValueCell.updateIfChanged(values.uFar, camera.far);
ValueCell.updateIfChanged(values.uNear, camera.near);
ValueCell.updateIfChanged(values.uFogFar, camera.fogFar);
ValueCell.updateIfChanged(values.uFogNear, camera.fogNear);
let needsUpdate = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
if (values.dOrthographic.ref.value !== orthographic) needsUpdate = true;
ValueCell.updateIfChanged(values.dOrthographic, orthographic);
const occlusion = props.occlusion.name === 'on';
if (values.dOcclusionEnable.ref.value !== occlusion) needsUpdate = true;
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusion);
if (props.occlusion.name === 'on') {
const { kernelSize } = props.occlusion.params;
if (values.dOcclusionKernelSize.ref.value !== kernelSize) needsUpdate = true;
ValueCell.updateIfChanged(values.dOcclusionKernelSize, kernelSize);
ValueCell.updateIfChanged(values.uOcclusionBias, props.occlusion.params.bias);
ValueCell.updateIfChanged(values.uOcclusionRadius, props.occlusion.params.radius);
}
const outline = props.outline.name === 'on';
if (values.dOutlineEnable.ref.value !== outline) needsUpdate = true;
ValueCell.updateIfChanged(values.dOutlineEnable, outline);
if (props.outline.name === 'on') {
ValueCell.updateIfChanged(values.uOutlineScale, props.outline.params.scale * this.webgl.pixelRatio);
ValueCell.updateIfChanged(values.uOutlineThreshold, props.outline.params.threshold);
this.outlinesTarget.bind();
this.outlinesRenderable.render();
}
if (needsUpdate) {
this.renderable.update();
}
if (props.occlusion.name === 'on') {
const { x, y, width, height } = camera.viewport;
const sx = Math.floor(x * this.ssaoScale);
const sy = Math.floor(y * this.ssaoScale);
const sw = Math.ceil(width * this.ssaoScale);
const sh = Math.ceil(height * this.ssaoScale);
this.webgl.gl.viewport(sx, sy, sw, sh);
this.webgl.gl.scissor(sx, sy, sw, sh);
if (props.antialiasing.name === 'on') {
this.tmpTarget.bind();
} else if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
} else {
this.target.bind();
}
this.ssaoFramebuffer.bind();
this.ssaoRenderable.render();
this.updateState(camera);
this.renderable.render();
}
this.ssaoBlurFirstPassFramebuffer.bind();
this.ssaoBlurFirstPassRenderable.render();
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name === 'off') return;
this.ssaoBlurSecondPassFramebuffer.bind();
this.ssaoBlurSecondPassRenderable.render();
const { values } = this.fxaa;
let needsUpdate = false;
const input = (props.occlusion.name === 'on' || props.outline.name === 'on')
? this.tmpTarget.texture : this.drawPass.colorTarget.texture;
if (values.tColor.ref.value !== input) {
ValueCell.update(this.fxaa.values.tColor, input);
needsUpdate = true;
}
const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props.antialiasing.params;
if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin);
if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax);
if (values.dIterations.ref.value !== iterations) needsUpdate = true;
ValueCell.updateIfChanged(values.dIterations, iterations);
if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true;
ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality);
if (needsUpdate) {
this.fxaa.update();
this.webgl.gl.viewport(x, y, width, height);
this.webgl.gl.scissor(x, y, width, height);
}
if (toDrawingBuffer) {
@@ -242,62 +525,75 @@ export class PostprocessingPass {
this.target.bind();
}
this.updateState(camera);
this.fxaa.render();
const { gl, state } = this.webgl;
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
this.renderable.render();
}
}
export class AntialiasingPass {
static isEnabled(props: PostprocessingProps) {
return props.antialiasing.name !== 'off';
}
private _render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'off') {
this._renderPostprocessing(camera, toDrawingBuffer, props);
}
readonly target: RenderTarget
private readonly fxaa: FxaaPass
private readonly smaa: SmaaPass
if (props.antialiasing.name === 'on') {
constructor(webgl: WebGLContext, private drawPass: DrawPass) {
const { colorTarget } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
this.target = webgl.createRenderTarget(width, height, false);
this.fxaa = new FxaaPass(webgl, this.target.texture);
this.smaa = new SmaaPass(webgl, this.target.texture);
}
setSize(width: number, height: number) {
const w = this.target.texture.getWidth();
const h = this.target.texture.getHeight();
if (width !== w || height !== h) {
this.target.setSize(width, height);
this.fxaa.setSize(width, height);
if (this.smaa.supported) this.smaa.setSize(width, height);
}
}
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name !== 'fxaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.fxaa.update(input, props.antialiasing.params);
this.fxaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
}
private _renderSmaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name !== 'smaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.smaa.update(input, props.antialiasing.params);
this.smaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
}
render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name === 'off') return;
if (props.antialiasing.name === 'fxaa') {
this._renderFxaa(camera, toDrawingBuffer, props);
}
}
render(camera: Camera | StereoCamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (StereoCamera.is(camera)) {
this._render(camera.left, toDrawingBuffer, props);
this._render(camera.right, toDrawingBuffer, props);
} else {
this._render(camera, toDrawingBuffer, props);
} else if (props.antialiasing.name === 'smaa') {
if (!this.smaa.supported) {
throw new Error('SMAA not supported, missing "HTMLImageElement"');
}
this._renderSmaa(camera, toDrawingBuffer, props);
}
}
}
//
const FxaaSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSizeInv: UniformSpec('v2'),
dEdgeThresholdMin: DefineSpec('number'),
dEdgeThresholdMax: DefineSpec('number'),
dIterations: DefineSpec('number'),
dSubpixelQuality: DefineSpec('number'),
};
const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag);
type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>>
function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof FxaaSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
dEdgeThresholdMin: ValueCell.create(0.0312),
dEdgeThresholdMax: ValueCell.create(0.125),
dIterations: ValueCell.create(12),
dSubpixelQuality: ValueCell.create(0.75),
};
const schema = { ...FxaaSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,8 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import quad_vert from '../../mol-gl/shader/quad.vert';
import evaluate_wboit_frag from '../../mol-gl/shader/evaluate-wboit.frag';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag';
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { Vec2 } from '../../mol-math/linear-algebra';
import { isDebugMode } from '../../mol-util/debug';
@@ -25,7 +25,7 @@ const EvaluateWboitSchema = {
tWboitB: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const EvaluateWboitShaderCode = ShaderCode('evaluate-wboit', quad_vert, evaluate_wboit_frag);
const EvaluateWboitShaderCode = ShaderCode('evaluate-wboit', quad_vert, evaluateWboit_frag);
type EvaluateWboitRenderable = ComputeRenderable<Values<typeof EvaluateWboitSchema>>
function getEvaluateWboitRenderable(ctx: WebGLContext, wboitATexture: Texture, wboitBTexture: Texture): EvaluateWboitRenderable {
@@ -51,9 +51,9 @@ export class WboitPass {
private readonly textureA: Texture
private readonly textureB: Texture
private _enabled = false;
get enabled() {
return this._enabled;
private _supported = false;
get supported() {
return this._supported;
}
bind() {
@@ -89,13 +89,44 @@ export class WboitPass {
}
}
constructor(private webgl: WebGLContext, width: number, height: number) {
const { resources, extensions } = webgl;
const { drawBuffers, textureFloat, colorBufferFloat, depthTexture } = extensions;
reset() {
if (this._supported) this._init();
}
private _init() {
const { extensions: { drawBuffers } } = this.webgl;
this.framebuffer.bind();
drawBuffers!.drawBuffers([
drawBuffers!.COLOR_ATTACHMENT0,
drawBuffers!.COLOR_ATTACHMENT1,
]);
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
}
static isSupported(webgl: WebGLContext) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, depthTexture } } = webgl;
if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers) {
if (isDebugMode) console.log('Missing extensions required for "wboit"');
return;
if (isDebugMode) {
const missing: string[] = [];
if (!textureFloat) missing.push('textureFloat');
if (!colorBufferFloat) missing.push('colorBufferFloat');
if (!depthTexture) missing.push('depthTexture');
if (!drawBuffers) missing.push('drawBuffers');
console.log(`Missing "${missing.join('", "')}" extensions required for "wboit"`);
}
return false;
} else {
return true;
}
}
constructor(private webgl: WebGLContext, width: number, height: number) {
if (!WboitPass.isSupported(webgl)) return;
const { resources } = webgl;
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
this.textureA.define(width, height);
@@ -104,17 +135,9 @@ export class WboitPass {
this.textureB.define(width, height);
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
this.framebuffer = resources.framebuffer();
this.framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,
drawBuffers.COLOR_ATTACHMENT1,
]);
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
this._enabled = true;
this._supported = true;
this._init();
}
}

View File

@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
let width = window.innerWidth;
let height = window.innerHeight;
if (container !== document.body) {
let bounds = container.getBoundingClientRect();
width = bounds.right - bounds.left;
height = bounds.bottom - bounds.top;
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
width = container.offsetWidth;
height = container.offsetHeight;
}
setCanvasSize(canvas, width, height, scale);
}

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Iterator from '../iterator';
import { Iterator } from '../iterator';
function iteratorToArray<T>(it: Iterator<T>): T[] {
const ret = [];

View File

@@ -5,9 +5,9 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Database from './db/database';
import Table from './db/table';
import Column from './db/column';
import { Database } from './db/database';
import { Table } from './db/table';
import { Column } from './db/column';
import * as ColumnHelpers from './db/column-helpers';
type DatabaseCollection<T extends Database.Schema> = { [name: string]: Database<T> }

View File

@@ -6,8 +6,8 @@
*/
import * as ColumnHelpers from '../column-helpers';
import Column from '../column';
import Table from '../table';
import { Column } from '../column';
import { Table } from '../table';
describe('column', () => {
const cc = Column.ofConst(10, 2, Column.Schema.int);

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Column from './column';
import { Column } from './column';
export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams<any>) {
const start = params && typeof params.start !== 'undefined' ? Math.max(Math.min(params.start, rowCount - 1), 0) : 0;

View File

@@ -230,7 +230,7 @@ namespace Column {
}
}
export default Column;
export { Column };
function createFirstIndexMapOfColumn<T>(c: Column<T>): Map<T, number> {
const map = new Map<T, number>();
@@ -364,7 +364,7 @@ function isIdentity(map: ArrayLike<number>, rowCount: number) {
}
function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> {
if (!c.isDefined || c.rowCount === 0) return c;
if (c.rowCount === 0) return c;
if (checkIdentity && isIdentity(map, c.rowCount)) return c;
if (!!c.__array && typeof c.value(0) === typeof c.__array[0]) return arrayView(c, map);
return viewFull(c, map);

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Table from './table';
import { Table } from './table';
/** A collection of tables */
type Database<Schema extends Database.Schema> = {
@@ -41,4 +41,4 @@ namespace Database {
}
}
export default Database;
export { Database };

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Column from './column';
import { Column } from './column';
import { sortArray } from '../util/sort';
import { StringBuilder } from '../../mol-util';
@@ -280,4 +280,4 @@ namespace Table {
}
}
export default Table;
export { Table };

View File

@@ -6,7 +6,7 @@
import * as DB from './db';
import * as Int from './int';
import Iterator from './iterator';
import { Iterator } from './iterator';
import * as Util from './util';
import * as Generic from './generic';

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