Compare commits

...

498 Commits

Author SHA1 Message Date
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
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
Alexander Rose
99a3906978 1.2.2 2020-11-26 11:17:22 -08:00
Alexander Rose
981db34736 Merge branch 'master' of https://github.com/molstar/molstar 2020-11-26 11:12:35 -08:00
Alexander Rose
c079a8c5a8 fixed triple linkstyle in visuals
- was ignored
2020-11-26 11:12:11 -08:00
Alexander Rose
ad70adf6ce improved & fixed fxaa
- enable linear texture interpolation to actually do subpixel fetches...
- higher quality fxaa profile with edge exploration
- exposed parameters
- enable during temproal multi sampling
2020-11-26 11:11:14 -08:00
David Sehnal
89110b52bd copyright info 2020-11-26 15:55:48 +01:00
David Sehnal
8a69f050a6 1.2.1 2020-11-26 15:28:12 +01:00
David Sehnal
9e38a44406 lint 2020-11-25 19:49:38 +01:00
David Sehnal
3514ab23c3 remove unused import 2020-11-25 17:08:08 +01:00
David Sehnal
b59e3c383d tweak 2020-11-25 17:05:38 +01:00
David Sehnal
eeba565d78 alpha-orbitals: fix async computation 2020-11-25 17:05:12 +01:00
David Sehnal
687e54cc87 alpha-orbitals: density support 2020-11-25 16:27:42 +01:00
David Sehnal
ac73939440 alpha-orbitals: data model improvements 2020-11-25 15:32:15 +01:00
David Sehnal
7a3eb8d03f fix Canvas3dInteractionHelper.leave 2020-11-25 11:00:05 +01:00
David Sehnal
3d26904e0b Merge branch 'master' of https://github.com/molstar/molstar into alpha-orbitals-density 2020-11-25 10:52:09 +01:00
Alexander Rose
468e14bc35 add fxaa option to postprocessing pass 2020-11-25 01:50:04 -08:00
David Sehnal
e2dc61212e alpha orbitals: density proof of concept 2020-11-24 15:12:53 +01:00
David Sehnal
aa911ad4bc viewer loadAllModelsOrAssemblyFromUrl options 2020-11-24 10:34:12 +01:00
Alexander Rose
bb5494264c wboit tweaks and fixes
- disable by default for now (until we settly on aa option)
- fix depth repcision issue in large scenes
- fix wrong depth tex bound
- undo pixelScale change as it was not doing anything
2020-11-23 23:47:54 -08:00
David Sehnal
c0116a3baa fix geometry quality access on empty structures 2020-11-23 22:38:29 +01:00
David Sehnal
9c7497b447 canvas3d: camera reset take 3 2020-11-23 22:31:37 +01:00
David Sehnal
fa3a79fdeb fix canvas3d camera reset condition 2020-11-23 22:21:33 +01:00
David Sehnal
2987240df4 canvas3d: do not autoreset camera if the "breaking" sphere mutually overlaps with current camera sphere 2020-11-23 22:11:35 +01:00
David Sehnal
17a1640da5 1.2.0 2020-11-23 14:11:05 +01:00
David Sehnal
a86da8ee11 mp4 encoder fixes 2020-11-23 14:04:48 +01:00
David Sehnal
20e373115d add xrayShaded option to orbital visuals 2020-11-23 13:33:28 +01:00
David Sehnal
531260fbc5 fix screenshot autocrop with transparent bg 2020-11-23 12:56:54 +01:00
David Sehnal
47a3dfcef9 enable wboit by default, use devicePixelRatio by default 2020-11-23 12:54:31 +01:00
Alexander Rose
c5ca51fd80 Merge branch 'master' into wboit 2020-11-21 23:30:37 -08:00
Alexander Rose
2f2e44c032 added xray-shaded option
- mesh geometry
- direct-volume geometry
- spheres geometry
2020-11-21 23:04:13 -08:00
Alexander Rose
26acb37098 remove transparency variant
- keep only single layer screndoor transparency
- fallback if wboit is not available
2020-11-21 22:06:35 -08:00
Alexander Rose
466308cde8 transparent object rendering improvement
- changed output to be pre-multiplied alpha
- point and text tweaks
- better handle opaque vs transparent volumes in blended rendering
2020-11-21 21:56:32 -08:00
Alexander Rose
dc9af9d8b0 fix drawing buffer uniform assignment
- fixes issues with texture fetches when size differs from canvas size
2020-11-21 11:25:41 -08:00
Alexander Rose
c17bfd65e7 ensure subtype assignment for all entities, #100 2020-11-21 11:13:15 -08:00
Alexander Rose
6de07ab8c2 interaction improvements
- add page xy to click event
- check if inside viewport before hover event
2020-11-21 11:11:59 -08:00
David Sehnal
0b8aab802c StateSelection improvements
- add findAncestor
- add findAncestorOfType
- add findRoot
2020-11-21 12:55:35 +01:00
David Sehnal
13f28fbe33 symmetryColor representation preset option 2020-11-19 14:18:20 +01:00
David Sehnal
2eda679966 Revert "Merge pull request #98 from AronKovacs/wboit"
- Move to a separate branch while issues are fixed.
2020-11-19 13:10:53 +01:00
David Sehnal
35c778b644 fix use-behavior bug 2020-11-19 11:48:09 +01:00
David Sehnal
96f8ba5a80 Merge pull request #98 from AronKovacs/wboit
Wboit
2020-11-18 11:38:00 +01:00
AronKovacs
0daaa94958 fix tests 2020-11-17 16:15:14 +01:00
AronKovacs
e672503fda fog patch by arose 2020-11-17 16:05:17 +01:00
AronKovacs
4d86c9e0ae renaming and bug fixes 2020-11-16 18:08:27 +01:00
AronKovacs
80fbc474f6 Merge pull request #2 from arose/wboit
wip, wboit integration
2020-11-16 13:50:45 +01:00
AronKovacs
dacdc6abfc Merge branch 'wboit' into wboit 2020-11-16 13:50:31 +01:00
Alexander Rose
58bc8b58de wip, wboit integration 2020-11-15 19:41:25 -08:00
AronKovacs
07ead670dd split drawpass._render() into _renderStandard() and _renderWboit() 2020-11-15 18:03:26 +01:00
David Sehnal
00fd760f71 1.1.33 2020-11-14 13:59:46 +01:00
David Sehnal
a71186905d animation improvements/fixes
- added AnimateStateSnapshots
- added "getAnimationDuration" to exportable animations
2020-11-14 13:38:39 +01:00
AronKovacs
a7e0524d01 Merge pull request #1 from arose/wboit
tweaks and fixes
2020-11-14 12:50:34 +01:00
David Sehnal
7d7c1241d4 fix undefined navigator.clipboard 2020-11-14 10:49:56 +01:00
Alexander Rose
1f6e928d78 Merge branch 'master' of https://github.com/molstar/molstar into wboit 2020-11-13 22:23:16 -08:00
Alexander Rose
9bc256bdab refactored wboit into seperate pass 2020-11-13 22:19:06 -08:00
Alexander Rose
734096260d add null-texture and use for tDepth 2020-11-13 20:52:42 -08:00
Alexander Rose
1b4b6f9435 add GlobalTextureSchema missing from renderables 2020-11-13 20:51:27 -08:00
David Sehnal
54fe5c85d6 1.1.32 2020-11-13 20:31:54 +01:00
David Sehnal
f336891bf3 Merge pull request #97 from molstar/mp4-export
MP4 Export
2020-11-13 20:27:28 +01:00
David Sehnal
d2a3c9c61f Merge branch 'master' of https://github.com/molstar/molstar into mp4-export 2020-11-13 20:24:48 +01:00
David Sehnal
6968959fe2 mp4 encoder fix background reset 2020-11-13 19:18:13 +01:00
David Sehnal
7749fe5000 fix encoder viewport check 2020-11-13 16:44:25 +01:00
David Sehnal
bf45d2df5d draw screenshot preview at ~8fps 2020-11-13 15:38:48 +01:00
David Sehnal
b2222844ae redraw preview on param change 2020-11-13 15:28:28 +01:00
Aron Kovacs
3d21f1ecc6 patch and background transparency 2020-11-13 15:01:57 +01:00
Alexander Rose
cde280de60 Merge pull request #96 from JonStargaryen/modelserverfixes
handle metal ions correctly for ModelServer ligand export
2020-11-12 22:02:25 -08:00
David Sehnal
9b415ddff2 screenshot frame touch support 2020-11-12 22:43:44 +01:00
David Sehnal
906c3ac2b6 screenshot fixes 2020-11-12 21:29:39 +01:00
JonStargaryen
498611d4d4 version & CHANGELOG 2020-11-12 12:02:25 -08:00
JonStargaryen
a11bc73d68 consistency 2020-11-12 11:52:10 -08:00
JonStargaryen
9616ae5d63 handle metal ion ligand export 2020-11-12 11:50:36 -08:00
Aron Kovacs
c81476d2a7 bug fixes and wboit for text and images 2020-11-12 18:31:09 +01:00
David Sehnal
397f001352 copy screenshot workaround for browsers without ClipboardItem support 2020-11-12 18:18:50 +01:00
Aron Kovacs
7edf274477 bug fixes and reenable rendering of the helpers 2020-11-12 17:13:24 +01:00
David Sehnal
3c1a26c4f5 basic overlay task support 2020-11-12 14:55:19 +01:00
David Sehnal
1c695846d5 mp4 animation export wip 2020-11-12 14:29:10 +01:00
David Sehnal
a4c6d1e0e6 mp4 export extension wip 2020-11-12 12:20:07 +01:00
David Sehnal
e51fe83800 screenshot controls 2020-11-12 11:56:41 +01:00
David Sehnal
316076d81e screenshot: autocrop 2020-11-12 09:50:46 +01:00
Aron Kovacs
4073055d8d better direct volume frag depth 2020-11-11 22:32:44 +01:00
David Sehnal
c6e0ec1c06 screenshot cropping 2020-11-11 20:33:08 +01:00
Aron Kovacs
49aaa48e6e bug fixes 2020-11-11 18:53:15 +01:00
David Sehnal
0eb882883e screenshot perf improvements 2020-11-11 17:24:12 +01:00
David Sehnal
a6c25551dd screenshot: copy to clipboard 2020-11-11 15:17:19 +01:00
David Sehnal
0a3f73860a mp4 encoder wip 2020-11-11 14:17:52 +01:00
David Sehnal
1de159d65c ImagePass custom viewport 2020-11-10 15:32:55 +01:00
David Sehnal
e2c411fefe pass state to animation teardown 2020-11-10 15:02:00 +01:00
David Sehnal
3cf1c64e12 Camera: fix "non-invertible-matrix" error when clearing state 2020-11-10 13:12:00 +01:00
David Sehnal
b159752b72 elementLabel edgecase 2020-11-10 13:08:06 +01:00
David Sehnal
0d7db59c9e Merge branch 'master' of https://github.com/molstar/molstar into mp4-export 2020-11-10 12:48:07 +01:00
David Sehnal
a8bf90a68b camera spin animation 2020-11-10 11:26:59 +01:00
David Sehnal
96aff39272 Canvas3d: tick manualDraw option 2020-11-10 09:42:51 +01:00
Aron Kovacs
a9ae08fc1f linting fixes 2020-11-09 18:03:08 +01:00
Aron Kovacs
a24f989c01 Merge branch 'master' into wboit 2020-11-09 17:58:23 +01:00
Aron Kovacs
41ff45d14c bug fixes 2020-11-09 17:54:22 +01:00
David Sehnal
6ad80bf66b mp4 encoder animation test 2020-11-09 13:55:14 +01:00
David Sehnal
eeed48a1f7 PluginAnimationLoop 2020-11-09 13:12:06 +01:00
David Sehnal
232bc0d076 wip animation loop 2020-11-09 12:51:55 +01:00
David Sehnal
ac6b87add4 wip animation loop 2020-11-09 12:28:34 +01:00
Alexander Rose
2e3bff7d48 wip, direct-volume rendering
- fix cellDim uniform
- add per unit gaussian-volume
- add option to try to jump over empty space
- fix complex structure visual not reusing renderObject
- add support for instance transforms
- only raymarch within intersection of bounding sphere a clip planes
2020-11-08 17:09:42 -08:00
Alexander Rose
bd223b4c39 added multiSample helper
- fixes multiSample pass wih more than one viewport
2020-11-08 16:00:00 -08:00
Aron Kovacs
a75dc11427 WBOIT init 2020-11-08 20:06:01 +01:00
David Sehnal
30acaffb72 Canvas3d.setCurrentTime 2020-11-06 11:58:33 +01:00
David Sehnal
2818102b8b tweak Camera.updateClip 2020-11-04 21:33:10 +01:00
David Sehnal
519e5a6f92 1.1.31 2020-11-04 20:49:51 +01:00
David Sehnal
071740e7c1 fix "zero radius" bounding sphere issue 2020-11-04 20:45:28 +01:00
David Sehnal
7aafb2f4c3 fix camera.near for small molecules 2020-11-04 19:50:35 +01:00
David Sehnal
3c72988d77 CreateOrbitalRepresentation3D.pickable option 2020-11-04 19:15:49 +01:00
David Sehnal
3c01dfbd42 mp4 export prototype 2020-11-04 14:34:24 +01:00
David Sehnal
3a5829aa3e add mp4 encoder 2020-11-04 09:38:58 +01:00
David Sehnal
ffeeddb37a debug.ts tweak 2020-11-02 21:02:23 +01:00
David Sehnal
50945493c1 fix canvas3d.setProps 2020-11-02 20:58:08 +01:00
David Sehnal
fa80c4797a 1.1.30 2020-11-02 19:19:24 +01:00
David Sehnal
650e8bf703 canvas3d.setProps fixes/improvements 2020-11-02 19:16:29 +01:00
David Sehnal
13d57737ae mol2 schema 2020-11-02 19:09:47 +01:00
David Sehnal
a6d1a3dfdd fix elements bounding spheres 2020-11-02 19:06:48 +01:00
David Sehnal
afffdc06e5 1.1.29 2020-11-02 14:59:32 +01:00
David Sehnal
80f1b1c795 volume-servery: local query add outputFilename param 2020-11-02 14:55:10 +01:00
David Sehnal
06111e2731 1.1.28 2020-11-02 10:45:48 +01:00
David Sehnal
adb49371bb fix build error 2020-11-02 10:43:01 +01:00
Alexander Rose
7b726ded20 gaussian density, render to float texture when available 2020-11-01 17:11:13 -08:00
Alexander Rose
9f85a0c840 better webgl context resource encapsulation
- added .namedComputeRenderables, .namedFramebuffer and .namedTexture
- use for shared resourced instead of storing in closure
- required to have multiple molstar webgl contexts simultaneously
2020-11-01 16:10:13 -08:00
Alexander Rose
f92755c920 factored out Camera.targetDistance 2020-11-01 14:09:16 -08:00
Alexander Rose
d141c27765 wip, direct-volume rendering
- better near/far clipping plane support
- use quality props in volume representations
2020-11-01 14:08:31 -08:00
Alexander Rose
062ac65f0f added pageX & pageY to WheelInput 2020-10-31 15:44:34 -07:00
Alexander Rose
bb420d0806 fix IndexPairBonds created with wrong count
- needs atom count (not bond count)
2020-10-31 15:42:18 -07:00
Alexander Rose
0018032423 detect AS and BR in guessElementSymbol 2020-10-31 15:40:46 -07:00
Alexander Rose
3dd48ac73c render passes refactoring
- simpler viewport handling
- shared render targets
- stereo camera fixes & improvements
2020-10-31 15:35:50 -07:00
David Sehnal
4632a6f305 Merge branch 'master' of https://github.com/molstar/molstar 2020-10-31 20:19:37 +01:00
David Sehnal
eda570d4f1 alpha-orbitals: re-enable cutoff in GPU shader 2020-10-31 20:19:13 +01:00
Alexander Rose
b0127d746d add bool uniform support 2020-10-31 12:10:15 -07:00
Alexander Rose
5a66ca69c4 tweaks/fixes
- avoid using global `name` property
- set Promise return value for TS 4.1 compat
- avoid tuple type names for TS <= 3.9 compat
2020-10-31 11:37:19 -07:00
Alexander Rose
1c17277f03 picking improvements
- get 3d position from depth
- option to render an object only in color pass
2020-10-31 11:34:44 -07:00
David Sehnal
d771bdc8ff Merge branch 'master' into alpha-orbitals 2020-10-31 18:58:55 +01:00
David Sehnal
eace3f4259 alpha-orbitals: webgl1 support 2020-10-31 18:48:05 +01:00
David Sehnal
8f88da70a6 alpha-orbitals: use square texture for GPU comp 2020-10-31 18:11:34 +01:00
David Sehnal
b797be9642 alpha-orbitals: refactoring 2020-10-31 17:59:14 +01:00
David Sehnal
2395b7a10a alpha-orbitals: force CPU computation 2020-10-31 17:08:51 +01:00
David Sehnal
0764795c08 alpha-orbitals: optimization & webgl1 support 2020-10-31 16:52:09 +01:00
David Sehnal
ea8b7a1d56 viewer.loadVolumeFromUrl 2020-10-30 11:03:21 +01:00
David Sehnal
7c6827f5f5 load all models or assembly preset 2020-10-30 10:21:26 +01:00
David Sehnal
5a6f16ef8d viewer: option to disable volume streaming 2020-10-30 10:01:54 +01:00
David Sehnal
8dfdcdd0b7 alpha-orbitals: gpu surface option 2020-10-28 20:04:34 +01:00
David Sehnal
67a2594108 immediate mode slider, alpha-orbitals improvements 2020-10-28 19:16:53 +01:00
David Sehnal
871f9635e3 alpha-orbitals: controls 2020-10-28 18:49:25 +01:00
David Sehnal
25251f3546 Merge pull request #92 from lidaof/master
Add chromosome and region query for g3d format
2020-10-28 16:00:55 +01:00
David Sehnal
98824f477e Merge pull request #94 from JonStargaryen/modelserverfixes
create-table: add protonation variants to cca.bcif
2020-10-27 22:26:36 +01:00
David Sehnal
aae9a117e8 alpha-orbitals: simplify shader 2020-10-27 22:23:13 +01:00
David Sehnal
452639c3ce alpha-orbitals: unused import 2020-10-27 21:41:35 +01:00
David Sehnal
6bd45e0a9b alpha-orbitals: improvements and fixes 2020-10-27 21:28:19 +01:00
David Sehnal
be100a3ac6 alpha-orbitals: optimize iso value computation 2020-10-27 20:09:26 +01:00
David Sehnal
96a8cd789c alpha-orbitals: optimization 2020-10-27 17:37:30 +01:00
David Sehnal
d195e1dbf5 alpha-orbitals: gpu wip 2020-10-27 15:38:08 +01:00
David Sehnal
e6a8e788f5 alpha-orbitals: add unit test 2020-10-26 18:07:57 +01:00
David Sehnal
a755ed441e alpha-orbitals: data model tweak 2020-10-26 15:37:52 +01:00
David Sehnal
de8f294329 alpha-orbitals extension and example 2020-10-24 16:32:40 +02:00
Daofeng Li
021171c07d expose chroms array to G3dInfoData for later data based decoration 2020-10-23 13:36:20 -05:00
David Sehnal
013ddb72ed multisample camera update fix 2020-10-21 09:05:48 +02:00
JonStargaryen
207c226f66 naming 2020-10-20 12:57:31 -07:00
JonStargaryen
b4ff98499b avoid overiding of entries for atom data 2020-10-20 12:36:36 -07:00
JonStargaryen
8471d337a2 add protvar atoms to cca.bcif 2020-10-20 12:31:02 -07:00
David Sehnal
2f84b94227 Basic stereo rendering support 2020-10-20 18:36:20 +02:00
Daofeng Li
8dcd6063b7 update according to @arose's review 2020-10-20 00:08:08 -05:00
Alexander Rose
caefe7ba67 wip, direct-volume rendering
- volume marking
- position iterator
- trilinear position/vertex color interpolation
- fix missing update on traceOnly param change
2020-10-18 22:40:34 -07:00
Alexander Rose
ad6cebc59b canvas3d: fixes for custom pixel-scale & viewport 2020-10-18 13:46:32 -07:00
Daofeng Li
e8d2e6d806 update chain-test as @arose suggested 2020-10-18 10:15:19 -05:00
Alexander Rose
39352c40d1 fix indention 2020-10-18 00:34:40 -07:00
Alexander Rose
9994262abc Merge branch 'master' of https://github.com/molstar/molstar 2020-10-18 00:25:41 -07:00
Alexander Rose
5bcf923381 canvas3d: custom pixel-scale & viewport 2020-10-18 00:25:00 -07:00
Alexander Rose
ab5dd0b733 webgl & canvas3d helpers
- webgl.clear()
- canvas3d.pause()
- canvas3d pickScale adustable on creation
2020-10-17 12:48:01 -07:00
Alexander Rose
5a8a6310f8 webgl resource reuse improvements
- reuse common compute renderables
- add dRenderVariant to existing schema object
2020-10-17 12:18:52 -07:00
Alexander Rose
a634c7a587 adjust lines mapping/vertex indices
- to work well with position iterator for coloring
2020-10-17 11:12:37 -07:00
Alexander Rose
353c5d6d95 set TrajectoryInfo & AsymIdCount to dynamic model property
- fixes model-index coloring
2020-10-17 11:10:49 -07:00
David Sehnal
92698c486c Fix MappedControl edge case 2020-10-15 09:11:06 +02:00
Daofeng Li
898dd1161d Update g3dRegionQuery function params 2020-10-13 09:05:51 -05:00
Daofeng Li
361f289d0e Add chromosome and region query for g3d format 2020-10-13 00:53:45 -05:00
Alexander Rose
b49d036fcd wip, volume rendering
- fix, unset UNPACK_FLIP_Y_WEBGL for 3D tex
- gaussian-volume visual
- fixed, slice visual picking
- fixed, direct-volume renderable picking use depth
2020-10-12 00:35:53 -07:00
Alexander Rose
cfee9d86c0 multi-sample pass improvements & fixes
- use float rt when possible
- test color-float-buffer support
- fix, limit samples per frame
- include camera-helper scene
2020-10-11 23:01:00 -07:00
Alexander Rose
92622dfbd7 fix element loci .toExpression for multi model structures, #56 2020-10-10 16:55:01 -07:00
Alexander Rose
80c2876350 ui, guard focus entry list against too many items 2020-10-10 16:53:21 -07:00
Alexander Rose
fb99f6db8a tighter spheres geo bounding sphere 2020-10-10 16:52:32 -07:00
Alexander Rose
862f8193ef wip, direct-volume-rendering
- support for flip-sided and double-sided
- add single-layer isosurface option
- fix webgl1 depth pass not clearing
- fix slow out-of-bounds access when creating texture
2020-10-04 14:01:42 -07:00
Alexander Rose
490c6679eb add support for vertex colors
use in color theme with
- PositionLocation
- 'vertex'/'vertexInstance' granularity
2020-10-04 00:36:34 -07:00
Alexander Rose
dd278ca964 model & repr update/selection fixes and tweaks 2020-10-03 17:27:41 -07:00
Alexander Rose
0892bb24d0 geometry building improements
- add and use PrimitiveBuilder.addQuad
- avoid namespace lookups for ribbon building
- allow triangluar prism
2020-10-03 11:52:31 -07:00
Alexander Rose
83ce5e9422 add config item for state history-capacity 2020-10-03 11:50:37 -07:00
Alexander Rose
c4ba92c7cb input-observer: allow some non pritable keys 2020-10-03 11:49:53 -07:00
David Sehnal
846bdf10b0 1.1.27 2020-10-03 18:50:04 +02:00
David Sehnal
91a46ea7df another Slider2 fix 2020-10-03 18:47:53 +02:00
David Sehnal
e4ec68a86c 1.1.26 2020-10-03 18:20:04 +02:00
David Sehnal
410655052f fix Slider2 bug 2020-10-03 18:16:06 +02:00
Alexander Rose
17162e967a fix create-ion, add chem-comp-dict util 2020-09-30 23:55:35 -07:00
Alexander Rose
c119a1bc21 Merge pull request #88 from McMenemy/extract_ion_names_from_ccd
Extract ion names from ccd
2020-09-30 23:18:41 -07:00
David Sehnal
7f2e98f714 1.1.25 2020-09-30 18:29:45 +02:00
David Sehnal
82f5d8be21 Merge pull request #89 from JonStargaryen/modelserver-transform
Add transform functionality to ModelServer
2020-09-30 18:24:15 +02:00
JonStargaryen
73fa675346 version & CHANGELOG 2020-09-30 09:15:26 -07:00
JonStargaryen
1f2812b2e3 standardize QueryParamType 2020-09-30 08:56:25 -07:00
JonStargaryen
6f05179db8 remove createMat4 2020-09-30 08:38:12 -07:00
McMenemy
fc8848e97c revert release filter 2020-09-29 21:43:39 -07:00
JonStargaryen
b991102bfa rename param info 2020-09-29 12:24:22 -07:00
JonStargaryen
e3768805a6 rename to transform 2020-09-29 12:21:52 -07:00
JonStargaryen
cdb65665a6 transform 2020-09-29 11:33:05 -07:00
David Sehnal
3765bc410c 1.1.24 2020-09-29 18:58:15 +02:00
David Sehnal
69bbd76f33 mol-plugin: fix initViewer when settings canvas3d props in spec 2020-09-29 18:54:32 +02:00
McMenemy
b61b3e1115 fix linter error 2020-09-29 08:53:26 -07:00
McMenemy
835f717e47 only keep REL ions 2020-09-29 08:35:51 -07:00
McMenemy
6a35a3ece0 change date to 2020 2020-09-28 19:41:45 -07:00
McMenemy
518621a1bd add command to README.md 2020-09-28 16:28:48 -07:00
McMenemy
51acfa1dce revert package-lock.json changes 2020-09-28 16:26:46 -07:00
McMenemy
5be6c9176a move to separate script 2020-09-28 16:23:19 -07:00
David Sehnal
dfa83c94f7 ParamDefinition.ValueRef defaultRef option 2020-09-28 21:12:21 +02:00
David Sehnal
67aedd4770 ParamDefinition.ValueRef 2020-09-28 20:20:20 +02:00
David Sehnal
346eb59da9 color list value offset support 2020-09-28 14:05:42 +02:00
Alexander Rose
3195594ef3 Merge branch 'master' of https://github.com/molstar/molstar 2020-09-27 23:56:54 -07:00
Alexander Rose
489b412308 wip, direct-volume rendering
- separate pass to check against depth texture
- support precalculated normals
- shading in volume mode
- fixed orthographic projection
- fixed skewed grid (David)
- step size prop
- isosurface picking (needs more work)
2020-09-27 23:56:06 -07:00
Alexander Rose
2d0e8d4ca0 geo & repr data update tweaks 2020-09-27 12:12:11 -07:00
Alexander Rose
27f94c81a2 applied event for animation manager 2020-09-27 12:08:48 -07:00
Alexander Rose
1e865ecacc key event for input-observer 2020-09-27 12:08:27 -07:00
McMenemy
f293a02485 better arg help text 2020-09-25 20:13:33 -07:00
McMenemy
ddf00600c6 add command to README.md 2020-09-25 20:00:52 -07:00
McMenemy
88cd639493 add code generated types ion file 2020-09-25 19:37:06 -07:00
McMenemy
0a30ed45f9 first pass working extracting ion names 2020-09-25 19:17:42 -07:00
Josh McMenemy
b5b282c141 Merge pull request #1 from molstar/master
merge molstar master
2020-09-25 16:57:50 -07:00
David Sehnal
c4c60cb263 1.1.23 2020-09-25 19:45:51 +02:00
JonStargaryen
e3cf4e928e propagate 2020-09-21 15:33:10 -07:00
JonStargaryen
d8a08ef900 normalize query param 2020-09-21 13:54:22 -07:00
JonStargaryen
8b8f3bf492 query param def for transformation matrix 2020-09-21 13:28:22 -07:00
David Sehnal
3983073d6c mol-plugin: fix canvas bg color when setting partial renderer params 2020-09-21 12:07:49 +02:00
David Sehnal
82b22fa3f2 fix highlightOn in basic-wrapper example 2020-09-21 11:38:41 +02:00
Alexander Rose
8a38f73771 more approximate math functions 2020-09-19 18:13:08 -07:00
Alexander Rose
37da82b138 atomic detail preset tweaks
- handle structures with low residue to element ratio
2020-09-19 18:10:53 -07:00
Alexander Rose
dd17cb23d9 support label for file param controls; check event.key as well 2020-09-19 18:09:52 -07:00
Alexander Rose
8f3afd9f7c try use spacegroup cell to estimate structure volume 2020-09-19 11:32:17 -07:00
Alexander Rose
6e39188f0b unitcell repr attachment parameter 2020-09-19 11:31:32 -07:00
Alexander Rose
667cacea12 various structure update related fixes- fixes #87- update on hierarchy changes- fix select mark on object update 2020-09-19 11:29:02 -07:00
Alexander Rose
49f0ec981c check js files as well with eslint
- moved ts eslint config into overrides
2020-09-19 11:13:13 -07:00
David Sehnal
a60d6e9223 1.1.22 2020-09-19 11:07:33 +02:00
David Sehnal
2d111c1e25 mol-plugin: ability to ignore loci in highlight/select behavior 2020-09-19 10:55:40 +02:00
Alexander Rose
874cde4f72 1.1.21 2020-09-14 10:09:38 -07:00
David Sehnal
826318760e Canvas3DParams.camera.manualReset 2020-09-14 18:51:24 +02:00
Alexander Rose
de790051aa schema updates
- note that ncs.id has changed from string to number
2020-09-13 02:54:12 -07:00
Alexander Rose
00bf75839e Merge branch 'master' of https://github.com/molstar/molstar 2020-09-13 02:26:38 -07:00
Alexander Rose
b9d4501dcc more fine-grained model/structure/unit updates 2020-09-13 02:25:49 -07:00
Alexander Rose
46fb1789b0 trajectory cell handling improvements 2020-09-12 17:06:58 -07:00
Alexander Rose
a1e8bf841b color theme tweaks, more acurate granularity 2020-09-12 14:53:40 -07:00
Alexander Rose
6662dbfdd6 use unit/structure boundary for interactions geo 2020-09-12 14:51:36 -07:00
Alexander Rose
39b9710d16 fix unneccessary render & geo updates
- uniforms & defines
- bufferSubData instead of bufferData
- scene remove
- drawCount
- geo builder
2020-09-12 14:50:14 -07:00
Alexander Rose
4fe303da72 add missing DMPC lipid 2020-09-12 14:42:19 -07:00
Alexander Rose
0662506d35 add code to experiment with linear RGB workflow 2020-09-12 14:41:23 -07:00
Alexander Rose
ea987f5601 package script tweaks
- serve: use gzip
- state: set default working dir
2020-09-12 14:35:17 -07:00
Alexander Rose
bcae586122 math geo helpers 2020-09-12 14:34:14 -07:00
David Sehnal
e56f188a12 TypeScript 4.0 & update packages 2020-09-12 12:40:15 +02:00
David Sehnal
8fda9beb7b Merge pull request #86 from molstar/dependabot/npm_and_yarn/node-fetch-2.6.1
Bump node-fetch from 2.6.0 to 2.6.1
2020-09-11 17:23:18 +02:00
dependabot[bot]
0f50a6682b Bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-10 20:13:07 +00:00
David Sehnal
524d38c450 fix bounding box bug 2020-09-10 12:43:47 +02:00
Alexander Rose
68a2e52355 increase mediumResidueCount threshold to 5000, #85 2020-09-10 00:31:05 -07:00
David Sehnal
447d1f940f Grid.getHistogram
+ do not exlude any code from main build because it turned off VS Code features in the affected files
2020-09-09 18:38:54 +02:00
David Sehnal
1cbb59b5d0 1.1.20 2020-09-09 17:52:31 +02:00
Alexander Rose
6f5bcdef90 sphere3d extrema fixes 2020-09-08 23:54:54 -07:00
David Sehnal
f6f1c5a350 mol-model: optimize atomGroups query 2020-09-09 08:16:01 +02:00
Alexander Rose
f6545c38be 1.1.19 2020-09-08 01:38:58 -07:00
Alexander Rose
53fe73d3ee Structure.ofModel: merge consecutive water chains 2020-09-08 01:34:20 -07:00
Alexander Rose
3bf5ab1ef7 psf parser: segment name as auth asym id 2020-09-08 01:13:24 -07:00
Alexander Rose
b4813ff866 remove tryAdjustBoundary for now
- not precise enough/too costly
2020-09-08 01:12:43 -07:00
Alexander Rose
845269e9a5 tryAdjustBoundary tweaks 2020-09-08 01:10:50 -07:00
Alexander Rose
59968d92ab 1.1.18 2020-09-07 18:30:23 -07:00
Alexander Rose
d5b7cd370b wip, direct-volume rendering
- re-enabled for volume data
- fix normal calc
- support negative isovalues
- flat-shaded support
- ignore-light support
- support cell picking & marking
- support depth calculation/write
- support fog
- support interior coloring
- maxSteps loop counter as uniform in webgl2
- fix shifted coordinates and boundary issues
- improved geo/repr params
2020-09-07 18:02:07 -07:00
Alexander Rose
b4434cce17 fix picking in webgl1 for isosurface and slice 2020-09-07 17:55:04 -07:00
Alexander Rose
e73227519b factored out/moved common volume helpers
- createIsoValueParam
- eachVolumeLoci
2020-09-07 13:35:37 -07:00
Alexander Rose
f8e6d5cbfb add option to disable hardware antialiasing- wastful for direct volume rendering 2020-09-07 13:29:36 -07:00
Alexander Rose
70bde8c899 bounding sphere calculation for transformed boxes
- add Sphere.fromDimensionsAndTransform
- use for unitcell cage & volume
- precise image bounding sphere
2020-09-07 13:06:23 -07:00
Alexander Rose
361dce2b96 more precise sphere shader sizes 2020-09-07 12:55:56 -07:00
Alexander Rose
c83ce28bf4 no need for fragDepth in image shader 2020-09-05 14:43:24 -07:00
Alexander Rose
b9a3620a4c get EXT_float_blend together with EXT_color_buffer_float
- firfox warns to do that for best support
2020-09-05 14:42:44 -07:00
Alexander Rose
a939a57811 only dispatch hover event on changes
- camera, input changes
- better handle empty picking ids
2020-09-05 14:41:45 -07:00
Alexander Rose
8388ee8f1e make putty repr independent of secondary structure
- yields performance when secondary structure would need to be calculated
2020-09-05 14:37:23 -07:00
Alexander Rose
befa40f5a2 more boundary calc tweaks
- lower thresholds for using coarse calc
2020-09-04 00:18:34 -07:00
Alexander Rose
36257e2b0f fix SelectLoci on object-updated checks 2020-09-03 23:22:02 -07:00
Alexander Rose
769022e88c avoid creating unneccessary visual props objects 2020-09-03 23:20:26 -07:00
Alexander Rose
51c180a8f4 boundary calculation optimizations
- fast path for single transform renderables
- check conformation when remapping unit
2020-09-03 23:18:45 -07:00
Alexander Rose
2ffc5dc5c0 mol-ql: account for source when extending to whole residues 2020-09-03 23:14:18 -07:00
Alexander Rose
431ba01117 check if renderable transform matrix has reflection
- force double-sided draw to ensure the front is drawn
2020-09-03 23:11:03 -07:00
Alexander Rose
c6a4350b81 removed unused Canvas3D.getPixelData
- use .getImagePass instead
2020-09-02 22:22:25 -07:00
Alexander Rose
9ef8d0c9f8 fix missing camera radiusMax update 2020-09-02 22:20:25 -07:00
Alexander Rose
e7606477c2 fix wrong texture3d_from_2d_linear shader chunk path 2020-09-02 22:18:40 -07:00
Alexander Rose
de093b5472 support origin and model as reference for model unitcell representation 2020-09-02 22:14:44 -07:00
Alexander Rose
88cd9184d8 add FormatPropertyProvider.delete 2020-09-02 22:12:14 -07:00
Alexander Rose
055c169c1f add dontCompose arg to StructureBuilder.addWithOperator 2020-09-02 22:11:45 -07:00
Alexander Rose
1988275695 workaround for 1 pixel texture issues
- always use at least 2x2 textures
2020-09-02 22:10:54 -07:00
Alexander Rose
82451bff00 mol2 fixes
- handle multi model files
- fix optional column parsing
- correct type symbol assignment
2020-09-02 22:10:02 -07:00
Alexander Rose
e6fd0202a6 bond & interaction fixes
- add all inter-bond from index-pair (indexA >= indexB is handled by eachUnit loop)
- add inter contacts only once (not twice)
- allow same index contacts when between different units
- more granular each loci marking for interactions and bonds
- use bounding sphere from structure for inter bonds
2020-09-02 22:08:25 -07:00
David Sehnal
c21d84dd62 1.1.17 2020-09-01 12:58:52 +02:00
David Sehnal
f392ac21cd mol-state: transaction rethrowErrors option 2020-09-01 12:55:41 +02:00
653 changed files with 111572 additions and 21092 deletions

View File

@@ -3,60 +3,21 @@
"browser": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
"sourceType": "module"
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
}
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/class-name-casing": "off",
"indent": "off",
"@typescript-eslint/indent": [
"error",
4
],
"@typescript-eslint/member-delimiter-style": [
"off",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"off",
null
],
"@typescript-eslint/type-annotation-spacing": "error",
"arrow-parens": [
"off",
"as-needed"
],
"brace-style": "off",
"@typescript-eslint/brace-style": [
"error",
"1tbs", { "allowSingleLine": true }
],
"comma-spacing": "off",
"@typescript-eslint/comma-spacing": "error",
"space-infix-ops": "error",
"comma-dangle": "off",
"eqeqeq": [
@@ -70,6 +31,66 @@
"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": [
{
"files": ["**/*.ts", "**/*.tsx"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/class-name-casing": "off",
"@typescript-eslint/indent": [
"error",
4
],
"@typescript-eslint/member-delimiter-style": [
"off",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"off",
null
],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/brace-style": [
"error",
"1tbs", { "allowSingleLine": true }
],
"@typescript-eslint/comma-spacing": "error"
}
}
]
}

View File

@@ -99,6 +99,11 @@ and navigate to `build/viewer`
node lib/commonjs/cli/lipid-params -o src/mol-model/structure/model/types/lipids.ts
**Ion names**
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
**GraphQL schemas**
node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml
@@ -172,3 +177,4 @@ 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)

View File

@@ -24,4 +24,6 @@
* 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)

47072
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "1.1.16",
"version": "2.0.0-dev.6",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -11,8 +11,8 @@
"url": "https://github.com/molstar/molstar/issues"
},
"scripts": {
"lint": "eslint ./**/*.{ts,tsx}",
"lint-fix": "eslint ./**/*.{ts,tsx} --fix",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm run lint && 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",
@@ -29,11 +29,11 @@
"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",
"serve": "http-server -p 1338",
"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",
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js",
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js --working-folder ./build/state --port 1339",
"preversion": "npm run test",
"version": "npm run build",
"postversion": "git push && git push --tags"
@@ -80,69 +80,71 @@
"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.9.1",
"@typescript-eslint/parser": "^3.9.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.7.0",
"cpx2": "^3.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.0.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.1",
"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.19.0",
"style-loader": "^1.2.1",
"ts-jest": "^26.2.0",
"typescript": "^3.9.7",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.3.2",
"node-sass": "^5.0.0",
"raw-loader": "^4.0.2",
"sass-loader": "^10.1.0",
"simple-git": "^2.25.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.2.3",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-version-file-plugin": "^0.4.0"
},
"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.7",
"@types/jest": "^25.2.3",
"@types/node": "^14.6.0",
"@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.46",
"@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",
"immer": "^7.0.7",
"h264-mp4-encoder": "^1.0.12",
"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.2",
"swagger-ui-dist": "^3.32.4",
"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

@@ -4,40 +4,40 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
const git = require('simple-git')
const path = require('path')
const fs = require("fs")
const fse = require("fs-extra")
const git = require('simple-git');
const path = require('path');
const fs = require("fs");
const fse = require("fs-extra");
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
const buildDir = path.resolve(__dirname, '../build/')
const deployDir = path.resolve(buildDir, 'deploy/')
const localPath = path.resolve(deployDir, 'molstar.github.io/')
const remoteUrl = "https://github.com/molstar/molstar.github.io.git";
const buildDir = path.resolve(__dirname, '../build/');
const deployDir = path.resolve(buildDir, 'deploy/');
const localPath = path.resolve(deployDir, 'molstar.github.io/');
function log(command, stdout, stderr) {
if (command) {
console.log('\n###', command)
stdout.pipe(process.stdout)
stderr.pipe(process.stderr)
console.log('\n###', command);
stdout.pipe(process.stdout);
stderr.pipe(process.stderr);
}
}
function copyViewer() {
console.log('\n###', 'copy viewer files')
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
const viewerDeployPath = path.resolve(localPath, 'viewer/')
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
console.log('\n###', 'copy viewer files');
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/');
const viewerDeployPath = path.resolve(localPath, 'viewer/');
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true });
}
if (!fs.existsSync(localPath)) {
console.log('\n###', 'create localPath')
fs.mkdirSync(localPath, { recursive: true })
console.log('\n###', 'create localPath');
fs.mkdirSync(localPath, { recursive: true });
}
process.chdir(localPath);
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
console.log('\n###', 'clone repository')
console.log('\n###', 'clone repository');
git()
.outputHandler(log)
.clone(remoteUrl, localPath)
@@ -45,9 +45,9 @@ if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
.exec(copyViewer)
.add(['-A'])
.commit('updated viewer')
.push()
.push();
} else {
console.log('\n###', 'update repository')
console.log('\n###', 'update repository');
git()
.outputHandler(log)
.fetch(['--all'])
@@ -55,5 +55,5 @@ if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
.exec(copyViewer)
.add(['-A'])
.commit('updated viewer')
.push()
.push();
}

View File

@@ -6,7 +6,8 @@
*/
import '../../mol-util/polyfill';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import './index.html';
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/commands';
@@ -69,9 +70,10 @@ class Viewer {
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
const defaultSpec = DefaultPluginSpec();
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions],
actions: [...defaultSpec.actions],
behaviors: [
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci, { mark: false }),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
@@ -81,8 +83,8 @@ 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,
@@ -90,14 +92,14 @@ class Viewer {
controlsDisplay: o.layoutControlsDisplay,
},
controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
...defaultSpec.layout && defaultSpec.layout.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...DefaultPluginSpec.components,
...defaultSpec.components,
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
viewport: {
view: ViewportComponent

View File

@@ -46,13 +46,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

@@ -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>
</html>

View File

@@ -46,12 +46,15 @@
}
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
if (debugMode) molstar.setDebugMode(debugMode);
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
var hideControls = getParam('hide-controls', '[^&]+').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: !disableWboit,
layoutShowControls: !hideControls,
viewportShowExpand: false,
pdbProvider: pdbProvider || 'pdbe',

View File

@@ -6,7 +6,8 @@
*/
import '../../mol-util/polyfill';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import './index.html';
import './embedded.html';
import './favicon.ico';
@@ -28,6 +29,14 @@ import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { Color } from '../../mol-util/color';
import { StateObjectSelector } from '../../mol-state';
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';
require('mol-plugin-ui/skin/light.scss');
@@ -45,7 +54,8 @@ const Extensions = {
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat)
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
};
const DefaultViewerOptions = {
@@ -58,6 +68,9 @@ const DefaultViewerOptions = {
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
disableAntialiasing: false,
pixelScale: 1,
enableWboit: true,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -66,6 +79,7 @@ const DefaultViewerOptions = {
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
@@ -76,15 +90,16 @@ export class Viewer {
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginSpec();
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions],
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: {
@@ -93,17 +108,20 @@ export class Viewer {
controlsDisplay: o.layoutControlsDisplay,
},
controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
...defaultSpec.layout && defaultSpec.layout.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...DefaultPluginSpec.components,
remoteState: o.layoutShowRemoteState ? 'default' : 'none'
...defaultSpec.components,
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
@@ -112,6 +130,7 @@ export class Viewer {
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider]
]
@@ -133,7 +152,7 @@ export class Viewer {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false) {
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
@@ -142,19 +161,28 @@ export class Viewer {
url: Asset.Url(url),
format: format as any,
isBinary,
options: params.source.params.options,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const plugin = this.plugin;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
}
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
loadPdb(pdb: string) {
loadPdb(pdb: string, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
@@ -168,7 +196,7 @@ export class Viewer {
params: PdbDownloadProvider[provider].defaultValue as any
}
},
options: params.source.params.options,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
@@ -205,4 +233,47 @@ export class Viewer {
}
}));
}
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { 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 repr = plugin.build().to(volume);
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 }
}));
}
await repr.commit();
});
}
handleResize() {
this.plugin.layout.events.updated.next();
}
}
export interface LoadStructureOptions {
representationParams?: StructureRepresentationPresetProvider.CommonParams
}
export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number
}

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Josh McMenemy <josh.mcmenemy@gmail.com>
*/
import * as argparse from 'argparse';
import * as path from 'path';
import util from 'util';
import fs from 'fs';
require('util.promisify').shim();
const writeFile = util.promisify(fs.writeFile);
import { DatabaseCollection } from '../../mol-data/db';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
import { ensureDataAvailable, readCCD } from './util';
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
const ionNames: string[] = [];
for (const k in ccd) {
const {chem_comp} = ccd[k];
if (chem_comp.name.value(0).toUpperCase().includes(' ION')) {
ionNames.push(chem_comp.id.value(0));
}
}
// these are extra ions that don't have ION in their name
ionNames.push('NCO', 'OHX');
return ionNames;
}
function writeIonNamesFile(filePath: string, ionNames: string[]) {
const output = `/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated ion names params file. Names extracted from CCD components.
*
* @author molstar/chem-comp-dict/create-table cli
*/
export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
`;
writeFile(filePath, output);
}
async function run(out: string, forceDownload = false) {
await ensureDataAvailable(forceDownload);
const ccd = await readCCD();
const ionNames = extractIonNames(ccd);
if (!fs.existsSync(path.dirname(out))) {
fs.mkdirSync(path.dirname(out));
}
writeIonNamesFile(out, ionNames);
}
const parser = new argparse.ArgumentParser({
addHelp: true,
description: 'Extract and save IonNames from CCD.'
});
parser.addArgument('out', {
help: 'Generated file output path.'
});
parser.addArgument([ '--forceDownload', '-f' ], {
action: 'storeTrue',
help: 'Force download of CCD and PVCD.'
});
interface Args {
out: string,
forceDownload?: boolean,
}
const args: Args = parser.parseArgs();
run(args.out, args.forceDownload);

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -9,70 +9,16 @@ import * as argparse from 'argparse';
import * as util from 'util';
import * as path from 'path';
import * as fs from 'fs';
import * as zlib from 'zlib';
import fetch from 'node-fetch';
require('util.promisify').shim();
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
import { Progress } from '../../mol-task';
import { Database, Table, DatabaseCollection } from '../../mol-data/db';
import { CIF } from '../../mol-io/reader/cif';
import { CifWriter } from '../../mol-io/writer/cif';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
import { SetUtils } from '../../mol-util/set';
import { DefaultMap } from '../../mol-util/map';
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
import { ccd_chemCompAtom_schema } from '../../mol-io/reader/cif/schema/ccd-extras';
export async function ensureAvailable(path: string, url: string) {
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
console.log(`downloading ${url}...`);
const data = await fetch(url);
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR);
}
if (url.endsWith('.gz')) {
await writeFile(path, zlib.gunzipSync(await data.buffer()));
} else {
await writeFile(path, await data.text());
}
console.log(`done downloading ${url}`);
}
}
export async function ensureDataAvailable() {
await ensureAvailable(CCD_PATH, CCD_URL);
await ensureAvailable(PVCD_PATH, PVCD_URL);
}
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
const parsed = await parseCif(await readFile(path, 'utf8'));
return CIF.toDatabaseCollection(schema, parsed.result);
}
export async function readCCD() {
return readFileAsCollection(CCD_PATH, CCD_Schema);
}
export async function readPVCD() {
return readFileAsCollection(PVCD_PATH, CCD_Schema);
}
async function parseCif(data: string | Uint8Array) {
const comp = CIF.parse(data);
console.time('parse cif');
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
console.timeEnd('parse cif');
if (parsed.isError) throw parsed;
return parsed;
}
export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
const encoder = CifWriter.createEncoder({ binary, encoderName: 'mol*' });
CifWriter.Encoder.writeDatabase(encoder, name, database);
return encoder.getData();
}
import { ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
type CCB = Table<CCD_Schema['chem_comp_bond']>
type CCA = Table<CCD_Schema['chem_comp_atom']>
@@ -81,6 +27,10 @@ function ccbKey(compId: string, atomId1: string, atomId2: string) {
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`;
}
function ccaKey(compId: string, atomId: string) {
return `${compId}:${atomId}`;
}
function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
for (let i = 0, il = ccb._rowCount; i < il; ++i) {
set.add(ccbKey(ccb.comp_id.value(i), ccb.atom_id_1.value(i), ccb.atom_id_2.value(i)));
@@ -90,7 +40,7 @@ function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
function addChemCompAtomToSet(set: Set<string>, cca: CCA) {
for (let i = 0, il = cca._rowCount; i < il; ++i) {
set.add(cca.atom_id.value(i));
set.add(ccaKey(cca.comp_id.value(i), cca.atom_id.value(i)));
}
return set;
}
@@ -136,11 +86,32 @@ function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
}
}
async function createBonds(atomsRequested: boolean) {
await ensureDataAvailable();
const ccd = await readCCD();
const pvcd = await readPVCD();
function checkAddingAtomsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
const ccaSetByParent = DefaultMap<string, Set<string>>(() => new Set());
for (const k in pvcd) {
const { chem_comp, chem_comp_atom } = pvcd[k];
if (chem_comp_atom._rowCount) {
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
if (parentIds.length === 0) {
const set = ccaSetByParent.getDefault(chem_comp.id.value(0));
addChemCompAtomToSet(set, chem_comp_atom);
} else {
for (let i = 0, il = parentIds.length; i < il; ++i) {
const parentId = parentIds[i];
const set = ccaSetByParent.getDefault(parentId);
addChemCompAtomToSet(set, chem_comp_atom);
}
}
}
}
}
async function createBonds(
ccd: DatabaseCollection<CCD_Schema>,
pvcd: DatabaseCollection<CCD_Schema>,
atomsRequested: boolean
) {
const ccbSet = new Set<string>();
const comp_id: string[] = [];
@@ -206,10 +177,12 @@ async function createBonds(atomsRequested: boolean) {
{ chem_comp_bond: bondTable }
);
return { bonds: bondDatabase, atoms: atomsRequested ? createAtoms(ccd) : void 0 };
return { bonds: bondDatabase, atoms: atomsRequested ? createAtoms(ccd, pvcd) : void 0 };
}
function createAtoms(ccd: DatabaseCollection<CCD_Schema>) {
function createAtoms(ccd: DatabaseCollection<CCD_Schema>, pvcd: DatabaseCollection<CCD_Schema>) {
const ccaSet = new Set<string>();
const comp_id: string[] = [];
const atom_id: string[] = [];
const charge: number[] = [];
@@ -217,10 +190,33 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>) {
function addAtoms(compId: string, cca: CCA) {
for (let i = 0, il = cca._rowCount; i < il; ++i) {
atom_id.push(cca.atom_id.value(i));
comp_id.push(compId);
charge.push(cca.charge.value(i));
pdbx_stereo_config.push(cca.pdbx_stereo_config.value(i));
const atomId = cca.atom_id.value(i);
const k = ccaKey(compId, atomId);
if (!ccaSet.has(k)) {
atom_id.push(atomId);
comp_id.push(compId);
charge.push(cca.charge.value(i));
pdbx_stereo_config.push(cca.pdbx_stereo_config.value(i));
ccaSet.add(k);
}
}
}
// check adding atoms from PVCD
checkAddingAtomsFromPVCD(pvcd);
// add atoms from PVCD
for (const k in pvcd) {
const { chem_comp, chem_comp_atom } = pvcd[k];
if (chem_comp_atom._rowCount) {
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
if (parentIds.length === 0) {
addAtoms(chem_comp.id.value(0), chem_comp_atom);
} else {
for (let i = 0, il = parentIds.length; i < il; ++i) {
addAtoms(parentIds[i], chem_comp_atom);
}
}
}
}
@@ -243,8 +239,12 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>) {
);
}
async function run(out: string, binary = false, ccaOut?: string) {
const { bonds, atoms } = await createBonds(!!ccaOut);
async function run(out: string, binary = false, forceDownload = false, ccaOut?: string) {
await ensureDataAvailable(forceDownload);
const ccd = await readCCD();
const pvcd = await readPVCD();
const { bonds, atoms } = await createBonds(ccd, pvcd, !!ccaOut);
const ccbCif = getEncodedCif(CCB_TABLE_NAME, bonds, binary);
if (!fs.existsSync(path.dirname(out))) {
@@ -264,12 +264,6 @@ async function run(out: string, binary = false, ccaOut?: string) {
const CCB_TABLE_NAME = 'CHEM_COMP_BONDS';
const CCA_TABLE_NAME = 'CHEM_COMP_ATOMS';
const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data');
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
const parser = new argparse.ArgumentParser({
addHelp: true,
description: 'Create a cif file with one big table of all chem_comp_bond entries from the CCD and PVCD.'
@@ -290,13 +284,11 @@ parser.addArgument(['--ccaOut', '-a'], {
required: false
});
interface Args {
out: string
forceDownload?: boolean
out: string,
forceDownload?: boolean,
binary?: boolean,
ccaOut?: string
}
const args: Args = parser.parseArgs();
const FORCE_DOWNLOAD = args.forceDownload;
run(args.out, args.binary, args.ccaOut);
run(args.out, args.binary, args.forceDownload, args.ccaOut);

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as util from 'util';
import * as path from 'path';
import * as fs from 'fs';
import * as zlib from 'zlib';
import fetch from 'node-fetch';
require('util.promisify').shim();
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
import { Progress } from '../../mol-task';
import { Database } from '../../mol-data/db';
import { CIF } from '../../mol-io/reader/cif';
import { CifWriter } from '../../mol-io/writer/cif';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
export async function ensureAvailable(path: string, url: string, forceDownload = false) {
if (forceDownload || !fs.existsSync(path)) {
console.log(`downloading ${url}...`);
const data = await fetch(url);
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR);
}
if (url.endsWith('.gz')) {
await writeFile(path, zlib.gunzipSync(await data.buffer()));
} else {
await writeFile(path, await data.text());
}
console.log(`done downloading ${url}`);
}
}
export async function ensureDataAvailable(forceDownload = false) {
await ensureAvailable(CCD_PATH, CCD_URL, forceDownload);
await ensureAvailable(PVCD_PATH, PVCD_URL, forceDownload);
}
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
const parsed = await parseCif(await readFile(path, 'utf8'));
return CIF.toDatabaseCollection(schema, parsed.result);
}
export async function readCCD() {
return readFileAsCollection(CCD_PATH, CCD_Schema);
}
export async function readPVCD() {
return readFileAsCollection(PVCD_PATH, CCD_Schema);
}
async function parseCif(data: string | Uint8Array) {
const comp = CIF.parse(data);
console.time('parse cif');
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
console.timeEnd('parse cif');
if (parsed.isError) throw parsed;
return parsed;
}
export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
const encoder = CifWriter.createEncoder({ binary, encoderName: 'mol*' });
CifWriter.Encoder.writeDatabase(encoder, name, database);
return encoder.getData();
}
const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data');
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';

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

@@ -31,6 +31,8 @@ async function ensureAvailable(path: string, url: string) {
async function ensureLipidsAvailable() { await ensureAvailable(MARTINI_LIPIDS_PATH, MARTINI_LIPIDS_URL); }
const extraLipids = ['DMPC'];
async function run(out: string) {
await ensureLipidsAvailable();
const lipidsItpStr = fs.readFileSync(MARTINI_LIPIDS_PATH, 'utf8');
@@ -44,17 +46,20 @@ async function run(out: string) {
UniqueArray.add(lipids, v, v);
}
for (const v of extraLipids) {
UniqueArray.add(lipids, v, v);
}
const lipidNames = JSON.stringify(lipids.array);
if (out) {
const output = `/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
*
* @author molstar/lipid-params cli
*/
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
*
* @author molstar/lipid-params cli
*/
export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g, ', ')});
`;

View File

@@ -26,6 +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 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

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
<Controls orbitals={orbitals} />
</PluginContextContainer>, parent);
}
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
const params = useBehavior(orbitals.params);
const values = useBehavior(orbitals.state);
return <ParameterControls params={params as any} values={values} onChangeValues={(vs: any) => orbitals.state.next(vs)} />;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Alpha Orbitals Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
#controls {
position: absolute;
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>
</head>
<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>
</body>
</html>

View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
import { createPluginAsync } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { PluginStateObject } from '../../mol-plugin-state/objects';
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 { PluginCommands } from '../../mol-plugin/commands';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
require('mol-plugin-ui/skin/light.scss');
interface DemoInput {
moleculeSdf: string,
basis: Basis,
order: SphericalBasisOrder,
orbitals: AlphaOrbital[]
}
interface Params {
show: { name: 'orbital', params: { index: number } } | { name: 'density', params: {} },
isoValue: number,
gpuSurface: boolean
}
type Selectors = {
type: 'orbital',
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>,
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
negative: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
} | {
type: 'density',
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalDensityVolume>,
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
}
export class AlphaOrbitalsExample {
plugin: PluginContext;
async init(target: string | HTMLElement) {
const defaultSpec = DefaultPluginSpec();
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
...defaultSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
components: {
viewport: {
canvas3d: {
camera: {
helper: { axes: { name: 'off', params: { } } }
}
}
}
},
config: [
[PluginConfig.Viewport.ShowExpand, false],
[PluginConfig.Viewport.ShowControls, false],
[PluginConfig.Viewport.ShowSelectionMode, false],
[PluginConfig.Viewport.ShowAnimation, false],
]
});
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
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).`
});
return;
}
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
});
mountControls(this, document.getElementById('controls')!);
}
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
private selectors?: Selectors = void 0;
private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;
private currentParams: Params = { ...this.state.value };
private clearVolume() {
if (!this.selectors) return;
const v = this.selectors.volume;
this.selectors = void 0;
return this.plugin.build().delete(v).commit();
}
private async syncVolume() {
if (!this.basis?.isOk) return;
const state = this.state.value;
if (state.show.name !== this.selectors?.type) {
await this.clearVolume();
}
const update = this.plugin.build();
if (state.show.name === 'orbital') {
if (!this.selectors) {
const volume = update
.to(this.basis)
.apply(CreateOrbitalVolume, { index: state.show.params.index });
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
const negative = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('negative', ColorNames.red)).selector;
this.selectors = { type: 'orbital', volume: volume.selector, positive, negative };
} else {
const index = state.show.params.index;
update.to(this.selectors.volume).update(CreateOrbitalVolume, () => ({ index }));
}
} else {
if (!this.selectors) {
const volume = update
.to(this.basis)
.apply(CreateOrbitalDensityVolume);
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
this.selectors = { type: 'density', volume: volume.selector, positive };
}
}
await update.commit();
if (this.currentParams.gpuSurface !== this.state.value.gpuSurface) {
await this.setIsovalue();
}
this.currentParams = this.state.value;
}
private setIsovalue() {
if (!this.selectors) return;
this.currentParams = this.state.value;
const update = this.plugin.build();
update.to(this.selectors.positive).update(this.volumeParams('positive', ColorNames.blue));
if (this.selectors?.type === 'orbital') {
update.to(this.selectors.negative).update(this.volumeParams('negative', ColorNames.red));
}
return update.commit();
}
private volumeParams(kind: 'positive' | 'negative', color: Color): StateTransformer.Params<typeof CreateOrbitalRepresentation3D> {
return {
alpha: 0.85,
color,
directVolume: this.state.value.gpuSurface,
kind,
relativeIsovalue: this.state.value.isoValue,
pickable: false,
xrayShaded: true
};
}
async load(input: DemoInput) {
await this.plugin.clear();
const data = await this.plugin.builders.data.rawData({ data: input.moleculeSdf }, { state: { isGhost: true } });
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, 'mol');
const model = await this.plugin.builders.structure.createModel(trajectory);
const structure = await this.plugin.builders.structure.createStructure(model);
const all = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'all');
if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
this.basis = await this.plugin.build().toRoot()
.apply(StaticBasisAndOrbitals, { basis: input.basis, order: input.order, orbitals: input.orbitals })
.commit();
await this.syncVolume();
this.params.next({
show: ParamDefinition.MappedStatic('orbital', {
'orbital': ParamDefinition.Group({
index: ParamDefinition.Numeric(32, { min: 0, max: input.orbitals.length - 1 }, { immediateUpdate: true, isEssential: true }),
}),
'density': ParamDefinition.EmptyGroup()
}, { cycle: true }),
isoValue: ParamDefinition.Numeric(this.currentParams.isoValue, { min: 0.5, max: 3, step: 0.1 }, { immediateUpdate: true, isEssential: false }),
gpuSurface: ParamDefinition.Boolean(this.currentParams.gpuSurface, { isHidden: true })
});
this.state.pipe(skip(1), debounceTime(1000 / 24)).subscribe(async params => {
if (params.show.name !== this.currentParams.show.name
|| (params.show.name === 'orbital' && this.currentParams.show.name === 'orbital' && params.show.params.index !== this.currentParams.show.params.index)) {
this.syncVolume();
} else if (params.isoValue !== this.currentParams.isoValue || params.gpuSurface !== this.currentParams.gpuSurface) {
this.setIsovalue();
}
});
}
}
(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();

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());

View File

@@ -6,10 +6,10 @@
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';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { Script } from '../../mol-script/script';
@@ -29,7 +29,7 @@ class BasicWrapper {
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginSpec(),
layout: {
initial: {
isExpanded: false,
@@ -83,13 +83,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()
}
}
@@ -113,8 +117,10 @@ class BasicWrapper {
interactivity = {
highlightOn: () => {
const data = this.plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data;
if (!data) return;
const seq_id = 7;
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
'group-by': Q.struct.atomProperty.macromolecular.residueKey()

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,7 +5,8 @@
*/
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
@@ -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,20 +53,24 @@ 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;
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,
...DefaultPluginSpec(),
layout: {
initial: {
isExpanded: false,
@@ -82,6 +85,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 +106,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 +118,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,8 +6,9 @@
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';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
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';
@@ -46,7 +47,7 @@ class MolStarProteopediaWrapper {
customColorList?: number[]
}) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginSpec(),
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()
}
}
@@ -403,7 +408,7 @@ class MolStarProteopediaWrapper {
},
download: async (type: 'molj' | 'molx' = 'molj', params?: PluginState.SnapshotParams) => {
const data = await this.plugin.managers.snapshot.serialize({ type, params });
download(data, `mol-star_state_${(name || getFormattedTime())}.${type}`);
download(data, `mol-star_state_${getFormattedTime()}.${type}`);
},
fetch: async (url: string, type: 'molj' | 'molx' = 'molj') => {
try {

View File

@@ -0,0 +1,214 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Box3D } from '../../../mol-math/geometry';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { RuntimeContext } from '../../../mol-task';
import { sphericalCollocation } from '../collocation';
import { Basis, CubeGridInfo } from '../data-model';
describe('alpha-orbitals-cubes', () => {
it('water', async () => {
const grid: CubeGridInfo = {
params: {
basis: _testBasis,
cutoffThreshold: 0,
sphericalOrder: 'cca-reverse',
boxExpand: 0,
gridSpacing: []
},
box: Box3D.create(Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)),
delta: Vec3.create(2, 2, 2),
dimensions: Vec3.create(2, 2, 2),
npoints: 8,
size: Vec3.create(2, 2, 2)
};
const matrix = await sphericalCollocation(grid, {
energy: 0,
occupancy: 0,
alpha: [-2.2623991420609075e-16, 0.6360205395000592, 0.6672122399886391, -0.3876927909355508, -1.6780131293332933e-16, 2.844782862661151e-16, 4.977960694176068e-19, -2.3945919908996803e-16]
}, RuntimeContext.Synchronous);
const expected = [-0.1451730622877498, 0.06479453956039086, -0.2777738736440713, -0.057116584776260436, 0.05929916178822645, 0.2742903371231049, -0.07221698722165386, 0.15389180241391376];
expect(matrix.length).toBe(expected.length);
for (let i = 0; i < matrix.length; i++) {
expect(Math.abs(matrix[i] - expected[i]) < 1e-6).toBe(true);
}
});
});
const _testBasis: Basis = {
'atoms': [
{
'center': [
0.025886090588624934,
0.019164790004065606,
-0.013539970104105408
] as Vec3,
'shells': [
{
'angularMomentum': [0],
'coefficients': [
[
-0.004151277818987536,
-0.02067024147993795,
-0.05150303336984537,
0.33462711739899537,
0.5621061300983125,
0.17129946969948573
]
],
'exponents': [
152.28769660788095,
27.928015215973073,
7.848374792384515,
1.1223350202705642,
0.5093846587907856,
0.24292266532510307
]
},
{
'angularMomentum': [1],
'coefficients': [
[
0.007924233646294425,
0.051441048251911314,
0.18984000600705359,
0.4049863191150474,
0.40123628611490797,
0.1051855189039082
]
],
'exponents': [
27.203421487167727,
7.09409912597673,
2.5383362605345954,
1.0610730767843852,
0.4851948916410433,
0.22938302550642545
]
}
]
},
{
'center': [
0.5082729578468134,
1.6880351220025265,
0.4963443067810461
] as Vec3,
'shells': [
{
'angularMomentum': [0],
'coefficients': [
[
0.009163596280542963,
0.04936149294292479,
0.16853830490998634,
0.37056279972195677,
0.4164915298246781,
0.13033408410772263
]
],
'exponents': [
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
},
{
'angularMomentum': [0],
'coefficients': [
[
-0.32279868167000036,
3.209629817295221,
2.4672629224617935,
-0.048487066612842224,
-0.2611850111200143,
-0.8917817597810863,
-1.9607480081275706,
-2.203769342520311,
-0.6896328935259993
]
],
'exponents': [
10.256286070314905,
0.6227965325875392,
0.2391007667853915,
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
}
]
},
{
'center': [
1.1367367844436005,
-0.47018519422670163,
-1.356802622574504
] as Vec3,
'shells': [
{
'angularMomentum': [0],
'coefficients': [
[
0.009163596280542963,
0.04936149294292479,
0.16853830490998634,
0.37056279972195677,
0.4164915298246781,
0.13033408410772263
]
],
'exponents': [
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
},
{
'angularMomentum': [0],
'coefficients': [
[
-0.32279868167000036,
3.209629817295221,
2.4672629224617935,
-0.048487066612842224,
-0.2611850111200143,
-0.8917817597810863,
-1.9607480081275706,
-2.203769342520311,
-0.6896328935259993
]
],
'exponents': [
10.256286070314905,
0.6227965325875392,
0.2391007667853915,
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
}
]
}
]
};

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Inspired by https://github.com/dgasmith/gau2grid.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { arrayMin } from '../../mol-util/array';
import { AlphaOrbital, CubeGridInfo } from './data-model';
import { normalizeBasicOrder, SphericalFunctions } from './spherical-functions';
export async function sphericalCollocation(
grid: CubeGridInfo,
orbital: AlphaOrbital,
taskCtx: RuntimeContext
) {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let baseCount = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
for (const L of shell.angularMomentum) {
if (L > 4) {
// TODO: will L > 4 be required? Would need to precompute more functions in that case.
throw new Error('Angular momentum L > 4 not supported.');
}
baseCount += 2 * L + 1;
}
}
}
const matrix = new Float32Array(grid.npoints);
let baseIndex = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
let amIndex = 0;
for (const L of shell.angularMomentum) {
const alpha = normalizeBasicOrder(
L,
orbital.alpha.slice(baseIndex, baseIndex + 2 * L + 1),
sphericalOrder
);
baseIndex += 2 * L + 1;
collocationBasis(
matrix,
grid,
L,
shell.coefficients[amIndex++],
shell.exponents,
atom.center,
cutoffThreshold,
alpha
);
if (taskCtx.shouldUpdate) {
await taskCtx.update({
message: 'Computing...',
current: baseIndex,
max: baseCount,
isIndeterminate: false,
});
}
}
}
}
return matrix;
}
function collocationBasis(
matrix: Float32Array,
grid: CubeGridInfo,
L: number,
coefficients: number[],
exponents: number[],
center: Vec3,
cutoffThreshold: number,
alpha: number[]
) {
const ncoeff = exponents.length;
const sphericalFunc = SphericalFunctions[L];
const cx = center[0],
cy = center[1],
cz = center[2];
const ny = grid.dimensions[1],
nz = grid.dimensions[2];
const gdx = grid.delta[0],
gdy = grid.delta[1],
gdz = grid.delta[2];
const sx = grid.box.min[0],
sy = grid.box.min[1],
sz = grid.box.min[2];
const cutoffRadius =
cutoffThreshold > 0
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents))
: 10000;
const cutoffSquared = cutoffRadius * cutoffRadius;
const radiusBox = getRadiusBox(grid, center, cutoffRadius);
const iMin = radiusBox[0][0],
jMin = radiusBox[0][1],
kMin = radiusBox[0][2];
const iMax = radiusBox[1][0],
jMax = radiusBox[1][1],
kMax = radiusBox[1][2];
for (let i = iMin; i <= iMax; i++) {
const x = sx + gdx * i - cx;
const oX = i * ny * nz;
for (let j = jMin; j <= jMax; j++) {
const y = sy + gdy * j - cy;
const oY = oX + j * nz;
for (let k = kMin; k <= kMax; k++) {
const z = sz + gdz * k - cz;
const R2 = x * x + y * y + z * z;
if (R2 > cutoffSquared) {
continue;
}
let gaussianSum = 0;
for (let c = 0; c < ncoeff; c++) {
gaussianSum +=
coefficients[c] * Math.exp(-exponents[c] * R2);
}
const sphericalSum = L === 0 ? alpha[0] : sphericalFunc(alpha, x, y, z);
matrix[k + oY] += gaussianSum * sphericalSum;
}
}
}
}
function getRadiusBox(grid: CubeGridInfo, center: Vec3, radius: number) {
const r = Vec3.create(radius, radius, radius);
const min = Vec3.scaleAndAdd(Vec3(), center, r, -1);
const max = Vec3.add(Vec3(), center, r);
Vec3.sub(min, min, grid.box.min);
Vec3.sub(max, max, grid.box.min);
Vec3.div(min, min, grid.delta);
Vec3.floor(min, min);
Vec3.max(min, min, Vec3());
Vec3.div(max, max, grid.delta);
Vec3.ceil(max, max);
Vec3.min(max, max, Vec3.subScalar(Vec3(), grid.dimensions, 1));
return [min, max];
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
import { Grid } from '../../mol-model/volume';
import { SphericalBasisOrder } from './spherical-functions';
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
exponents: number[];
angularMomentum: number[];
// number[] for each angular momentum
coefficients: number[][];
}
export interface Basis {
atoms: {
// in Bohr units!
center: Vec3;
shells: SphericalElectronShell[];
}[];
}
export interface AlphaOrbital {
energy: number;
occupancy: number;
alpha: number[];
}
export interface CubeGridComputationParams {
basis: Basis;
/**
* for each electron shell compute a cutoff radius as
* const cutoffRadius = Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents));
*/
cutoffThreshold: number;
sphericalOrder: SphericalBasisOrder;
boxExpand: number;
gridSpacing: number | [atomCountThreshold: number, spacing: number][];
doNotComputeIsovalues?: boolean;
}
export interface CubeGridInfo {
params: CubeGridComputationParams;
dimensions: Vec3;
box: Box3D;
size: Vec3;
npoints: number;
delta: Vec3;
}
export interface CubeGrid {
grid: Grid;
isovalues?: { negative?: number; positive?: number };
}
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const geometry = params.basis.atoms.map(a => a.center);
const { gridSpacing: spacing, boxExpand: expand } = params;
const count = geometry.length;
const box = Box3D.expand(
Box3D(),
Box3D.fromVec3Array(Box3D(), geometry),
Vec3.create(expand, expand, expand)
);
const size = Box3D.size(Vec3(), box);
const spacingThresholds =
typeof spacing === 'number' ? [[0, spacing]] : [...spacing];
spacingThresholds.sort((a, b) => b[0] - a[0]);
let s = 0.4;
for (let i = 0; i <= spacingThresholds.length; i++) {
s = spacingThresholds[i][1];
if (spacingThresholds[i][0] <= count) break;
}
const dimensions = Vec3.ceil(Vec3(), Vec3.scale(Vec3(), size, 1 / s));
return {
params,
box,
dimensions,
size,
npoints: dimensions[0] * dimensions[1] * dimensions[2],
delta: Vec3.div(Vec3(), size, Vec3.subScalar(Vec3(), dimensions, 1)),
};
}
const BohrToAngstromFactor = 0.529177210859;
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
const boxSize = Box3D.size(Vec3(), gridInfo.box);
const boxOrigin = Vec3.clone(gridInfo.box.min);
Vec3.scale(boxSize, boxSize, BohrToAngstromFactor);
Vec3.scale(boxOrigin, boxOrigin, BohrToAngstromFactor);
const scale = Mat4.fromScaling(
Mat4(),
Vec3.div(
Vec3(),
boxSize,
Vec3.sub(Vec3(), gridInfo.dimensions, Vec3.create(1, 1, 1))
)
);
const translate = Mat4.fromTranslation(Mat4(), boxOrigin);
const matrix = Mat4.mul(Mat4(), translate, scale);
const grid: Grid = {
transform: { kind: 'matrix', matrix },
cells: Tensor.create(
Tensor.Space(gridInfo.dimensions, axisOrder, Float32Array),
(values as any) as Tensor.Data
),
stats: {
min: arrayMin(values),
max: arrayMax(values),
mean: arrayMean(values),
sigma: arrayRms(values),
},
};
return grid;
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
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 { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
export function createSphericalCollocationDensityGrid(
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
): Task<CubeGrid> {
return Task.create('Spherical Collocation Grid', async (ctx) => {
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
// console.timeEnd('gpu');
} else {
throw new Error('Missing OES_texture_float WebGL extension.');
}
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
let isovalues: { negative?: number, positive?: number } | undefined;
if (!params.doNotComputeIsovalues) {
isovalues = computeDensityIsocontourValues(matrix, 0.85);
}
return { grid, isovalues };
});
}
export function computeDensityIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
let weightSum = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = Math.abs(v);
weightSum += w;
}
const avgWeight = weightSum / input.length;
let minWeight = 3 * avgWeight;
// do not try to identify isovalues for degenerate data
// e.g. all values are almost same
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
return { negative: void 0, positive: void 0 };
}
let size = 0;
while (true) {
let csum = 0;
size = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = Math.abs(v);
if (w >= minWeight) {
csum += w;
size++;
}
}
if (csum / weightSum > cumulativeThreshold) {
break;
}
minWeight -= avgWeight;
}
const values = new Float32Array(size);
const weights = new Float32Array(size);
const indices = new Int32Array(size);
let o = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = Math.abs(v);
if (w >= minWeight) {
values[o] = v;
weights[o] = w;
indices[o] = o;
o++;
}
}
sortArray(
indices,
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
);
let cweight = 0,
cutoffIndex = 0;
for (let i = 0; i < size; i++) {
cweight += weights[indices[i]];
if (cweight / weightSum >= cumulativeThreshold) {
cutoffIndex = i;
break;
}
}
let positive = Number.POSITIVE_INFINITY,
negative = Number.NEGATIVE_INFINITY;
for (let i = 0; i < cutoffIndex; i++) {
const v = values[indices[i]];
if (v > 0) {
if (v < positive) positive = v;
} else if (v < 0) {
if (v > negative) negative = v;
}
}
return {
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
};
}

View File

@@ -0,0 +1,170 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { RuntimeContext } from '../../../mol-task';
import { ValueCell } from '../../../mol-util';
import { arrayMin } from '../../../mol-util/array';
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
import { MAIN, UTILS } from './shader.frag';
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'),
uNCenters: UniformSpec('i'),
uNAlpha: UniformSpec('i'),
uNCoeff: UniformSpec('i'),
uMaxCoeffs: UniformSpec('i'),
};
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);
let aO = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
for (const L of shell.angularMomentum) {
const a0 = normalizeBasicOrder(L, alphaOrbitals.slice(aO, aO + 2 * L + 1), sphericalOrder);
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
aO += 2 * L + 1;
}
}
}
return alpha;
}
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let centerCount = 0;
let baseCount = 0;
let coeffCount = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
for (const L of shell.angularMomentum) {
if (L > 4) {
// TODO: will L > 4 be required? Would need to precompute more functions in that case.
throw new Error('Angular momentum L > 4 not supported.');
}
centerCount++;
baseCount += 2 * L + 1;
coeffCount += shell.exponents.length;
}
}
}
const centers = new Float32Array(4 * centerCount);
// L, alpha_offset, coeff_offset_start, coeff_offset_end
const info = new Float32Array(4 * centerCount);
const alpha = new Float32Array(baseCount);
const coeff = new Float32Array(3 * coeffCount);
let maxCoeffs = 0;
let cO = 0, aO = 0, coeffO = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
let amIndex = 0;
for (const L of shell.angularMomentum) {
const a0 = normalizeBasicOrder(L, orbital.alpha.slice(aO, aO + 2 * L + 1), sphericalOrder);
const cutoffRadius = cutoffThreshold > 0
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(shell.exponents))
: 10000;
centers[4 * cO + 0] = atom.center[0];
centers[4 * cO + 1] = atom.center[1];
centers[4 * cO + 2] = atom.center[2];
centers[4 * cO + 3] = cutoffRadius * cutoffRadius;
info[4 * cO + 0] = L;
info[4 * cO + 1] = aO;
info[4 * cO + 2] = coeffO;
info[4 * cO + 3] = coeffO + shell.exponents.length;
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
const c0 = shell.coefficients[amIndex++];
for (let i = 0; i < shell.exponents.length; i++) {
coeff[3 * (coeffO + i) + 0] = c0[i];
coeff[3 * (coeffO + i) + 1] = shell.exponents[i];
}
if (c0.length > maxCoeffs) {
maxCoeffs = c0.length;
}
cO++;
aO += 2 * L + 1;
coeffO += shell.exponents.length;
}
}
}
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 },
};
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export const UTILS = `
float L1(vec3 p, float a0, float a1, float a2) {
return a0 * p.z + a1 * p.x + a2 * p.y;
}
float L2(vec3 p, float a0, float a1, float a2, float a3, float a4) {
float x = p.x, y = p.y, z = p.z;
float xx = x * x, yy = y * y, zz = z * z;
return (
a0 * (-0.5 * xx - 0.5 * yy + zz) +
a1 * (1.7320508075688772 * x * z) +
a2 * (1.7320508075688772 * y * z) +
a3 * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
a4 * (1.7320508075688772 * x * y)
);
}
float L3(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, float a6) {
float x = p.x, y = p.y, z = p.z;
float xx = x * x, yy = y * y, zz = z * z;
float xxx = xx * x, yyy = yy * y, zzz = zz * z;
return (
a0 * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
a1 * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
a2 * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
a3 * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
a4 * (3.872983346207417 * x * y * z) +
a5 * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
a6 * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
);
}
float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8) {
float x = p.x, y = p.y, z = p.z;
float xx = x * x, yy = y * y, zz = z * z;
float xxx = xx * x, yyy = yy * y, zzz = zz * z;
float xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
return (
a0 * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
a1 * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
a2 * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
a3 * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
a4 * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
a5 * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
a6 * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
a7 * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
a8 * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
);
}
float alpha(float offset, float f) {
#ifdef WEBGL1
// in webgl1, the value is in the alpha channel!
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
#else
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
#endif
}
float Y(int L, vec3 X, float aO, float fA) {
if (L == 0) {
return alpha(aO, fA);
} else if (L == 1) {
return L1(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA)
);
} else if (L == 2) {
return L2(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA)
);
} else if (L == 3) {
return L3(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA),
alpha(aO + 5.0, fA), alpha(aO + 6.0, fA)
);
} else if (L == 4) {
return L4(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA),
alpha(aO + 5.0, fA), alpha(aO + 6.0, fA), alpha(aO + 7.0, fA), alpha(aO + 8.0, fA)
);
}
// TODO: do we need L > 4?
return 0.0;
}
#ifndef WEBGL1
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
for (int i = start; i < end; i++) {
vec2 c = texture2D(tCoeff, vec2(float(i) * fCoeff, 0.5)).xy;
gauss += c.x * exp(-c.y * R2);
}
return gauss;
}
#else
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
int o = start;
for (int i = 0; i < uMaxCoeffs; i++) {
if (o >= end) break;
vec2 c = texture2D(tCoeff, vec2(float(o) * fCoeff, 0.5)).xy;
gauss += c.x * exp(-c.y * R2);
o++;
}
return gauss;
}
#endif
`;
export const MAIN = `
float fCenter = 1.0 / float(uNCenters - 1);
float fCoeff = 1.0 / float(uNCoeff - 1);
float fA = 1.0 / float(uNAlpha - 1);
float v = 0.0;
for (int i = 0; i < uNCenters; i++) {
vec2 cCoord = vec2(float(i) * fCenter, 0.5);
vec4 center = texture2D(tCenters, cCoord);
vec3 X = xyz - center.xyz;
float R2 = dot(X, X);
// center.w is squared cutoff radius
if (R2 > center.w) {
continue;
}
vec4 info = texture2D(tInfo, cCoord);
int L = int(info.x);
float aO = info.y;
int coeffStart = int(info.z);
int coeffEnd = int(info.w);
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
}
`;

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Inspired by https://github.com/dgasmith/gau2grid.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
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 { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
// setDebugMode(true);
export function createSphericalCollocationGrid(
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
): Task<CubeGrid> {
return Task.create('Spherical Collocation Grid', async (ctx) => {
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
// console.timeEnd('gpu');
} else {
// console.time('cpu');
matrix = await sphericalCollocation(cubeGrid, orbital, ctx);
// console.timeEnd('cpu');
}
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
let isovalues: { negative?: number, positive?: number } | undefined;
if (!params.doNotComputeIsovalues) {
isovalues = computeOrbitalIsocontourValues(matrix, 0.85);
}
return { grid, isovalues };
});
}
export function computeOrbitalIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
let weightSum = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = v * v;
weightSum += w;
}
const avgWeight = weightSum / input.length;
let minWeight = 3 * avgWeight;
// do not try to identify isovalues for degenerate data
// e.g. all values are almost same
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
return { negative: void 0, positive: void 0 };
}
let size = 0;
while (true) {
let csum = 0;
size = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = v * v;
if (w >= minWeight) {
csum += w;
size++;
}
}
if (csum / weightSum > cumulativeThreshold) {
break;
}
minWeight -= avgWeight;
}
const values = new Float32Array(size);
const weights = new Float32Array(size);
const indices = new Int32Array(size);
let o = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = v * v;
if (w >= minWeight) {
values[o] = v;
weights[o] = w;
indices[o] = o;
o++;
}
}
sortArray(
indices,
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
);
let cweight = 0,
cutoffIndex = 0;
for (let i = 0; i < size; i++) {
cweight += weights[indices[i]];
if (cweight / weightSum >= cumulativeThreshold) {
cutoffIndex = i;
break;
}
}
let positive = Number.POSITIVE_INFINITY,
negative = Number.NEGATIVE_INFINITY;
for (let i = 0; i < cutoffIndex; i++) {
const v = values[indices[i]];
if (v > 0) {
if (v < positive) positive = v;
} else if (v < 0) {
if (v > negative) negative = v;
}
}
return {
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
};
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Inspired by https://github.com/dgasmith/gau2grid.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
// gaussian:
// R_0, R^+_1, R^-_1, ..., R^+_l, R^-_l
// cca:
// R^-_(l), R^-_(l-1), ..., R_0, ..., R^+_(l-1), R^+_l
// cca-reverse:
// R^+_(l), R^+_(l-1), ..., R_0, ..., R^-_(l-1), R^-_l
export type SphericalBasisOrder = 'gaussian' | 'cca' | 'cca-reverse';
export function normalizeBasicOrder(
L: number,
alpha: number[],
order: SphericalBasisOrder
) {
if (order === 'gaussian' || L === 0) return alpha;
const ret: number[] = [alpha[L]];
for (let l = 0; l < L; l++) {
const a = alpha[L - l - 1],
b = alpha[L + l + 1];
if (order === 'cca') ret.push(b, a);
else ret.push(a, b);
}
return ret;
}
export type SphericalFunc = (
alpha: number[],
x: number,
y: number,
z: number
) => number;
export const SphericalFunctions: SphericalFunc[] = [L0, L1, L2, L3, L4];
// L_i functions were auto-generated.
function L0(alpha: number[], x: number, y: number, z: number) {
return alpha[0];
}
function L1(alpha: number[], x: number, y: number, z: number) {
return alpha[0] * z + alpha[1] * x + alpha[2] * y;
}
function L2(alpha: number[], x: number, y: number, z: number) {
const xx = x * x, yy = y * y, zz = z * z;
return (
alpha[0] * (-0.5 * xx - 0.5 * yy + zz) +
alpha[1] * (1.7320508075688772 * x * z) +
alpha[2] * (1.7320508075688772 * y * z) +
alpha[3] * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
alpha[4] * (1.7320508075688772 * x * y)
);
}
function L3(alpha: number[], x: number, y: number, z: number) {
const xx = x * x, yy = y * y, zz = z * z;
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
return (
alpha[0] * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
alpha[1] * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
alpha[2] * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
alpha[3] * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
alpha[4] * (3.872983346207417 * x * y * z) +
alpha[5] * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
alpha[6] * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
);
}
function L4(alpha: number[], x: number, y: number, z: number) {
const xx = x * x, yy = y * y, zz = z * z;
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
const xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
return (
alpha[0] * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
alpha[1] * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
alpha[2] * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
alpha[3] * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
alpha[4] * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
alpha[5] * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
alpha[6] * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
alpha[7] * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
alpha[8] * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
);
}

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
import { createSphericalCollocationGrid } from './orbitals';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Task } from '../../mol-task';
import { CustomProperties } from '../../mol-model/custom-property';
import { SphericalBasisOrder } from './spherical-functions';
import { Volume } from '../../mol-model/volume';
import { PluginContext } from '../../mol-plugin/context';
import { ColorNames } from '../../mol-util/color/names';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
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 { 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' }) { }
export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
name: 'static-basis-and-orbitals',
display: 'Basis and Orbitals',
from: PluginStateObject.Root,
to: BasisAndOrbitals,
params: {
label: PD.Text('Orbital Data', { isHidden: true }),
basis: PD.Value<Basis>(void 0 as any, { isHidden: true }),
order: PD.Text<SphericalBasisOrder>('gaussian' as SphericalBasisOrder, { isHidden: true }),
orbitals: PD.Value<AlphaOrbital[]>([], { isHidden: true })
},
})({
apply({ params }) {
return new BasisAndOrbitals({ basis: params.basis, order: params.order, orbitals: params.orbitals }, { label: params.label });
}
});
const CreateOrbitalVolumeParamBase = {
cutoffThreshold: PD.Numeric(0.0015, { min: 0, max: 0.1, step: 0.0001 }),
boxExpand: PD.Numeric(4.5, { min: 0, max: 7, step: 0.1 }),
gridSpacing: PD.ObjectList({ atomCount: PD.Numeric(0), spacing: PD.Numeric(0.35, { min: 0.1, max: 2, step: 0.01 }) }, e => `Atoms ${e.atomCount}: ${e.spacing}`, {
defaultValue: [
{ atomCount: 55, spacing: 0.5 },
{ atomCount: 40, spacing: 0.45 },
{ 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',
from: BasisAndOrbitals,
to: PluginStateObject.Volume.Data,
params: (a) => {
if (!a) {
return { index: PD.Numeric(0), ...CreateOrbitalVolumeParamBase };
}
return {
index: PD.Select(0, a.data.orbitals.map((o, i) => [i, `[${i + 1}] ${o.energy.toFixed(4)}`])),
...CreateOrbitalVolumeParamBase
};
}
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Orbital Volume', async ctx => {
const data = await createSphericalCollocationGrid({
basis: a.data.basis,
cutoffThreshold: params.cutoffThreshold,
sphericalOrder: a.data.order,
boxExpand: params.boxExpand,
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', 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' });
});
}
});
export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-density-volume',
display: 'Orbital Density Volume',
from: BasisAndOrbitals,
to: PluginStateObject.Volume.Data,
params: CreateOrbitalVolumeParamBase
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Orbital Volume', async ctx => {
const data = await createSphericalCollocationDensityGrid({
basis: a.data.basis,
cutoffThreshold: params.cutoffThreshold,
sphericalOrder: a.data.order,
boxExpand: params.boxExpand,
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', 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' });
});
}
});
export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
name: 'create-orbital-representation-3d',
display: 'Orbital Representation 3D',
from: PluginStateObject.Volume.Data,
to: PluginStateObject.Volume.Representation3D,
params: {
directVolume: PD.Boolean(false),
relativeIsovalue: PD.Numeric(1, { min: 0.01, max: 5, step: 0.01 }),
kind: PD.Select<'positive' | 'negative'>('positive', [['positive', 'Positive'], ['negative', 'Negative']]),
color: PD.Color(ColorNames.blue),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
xrayShaded: PD.Boolean(false),
pickable: PD.Boolean(true)
}
})({
canAutoUpdate() {
return true;
},
apply({ a, params: srcParams }, plugin: PluginContext) {
return Task.create('Orbitals Representation 3D', async ctx => {
const params = volumeParams(plugin, a, srcParams);
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
const provider = plugin.representation.volume.registry.get(params.type.name);
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
const props = params.type.params || {};
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
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) });
});
},
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
return Task.create('Orbitals Representation 3D', async ctx => {
const newParams = volumeParams(plugin, a, srcParams);
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.repr.setState({ pickable: srcParams.pickable });
b.description = VolumeRepresentation3DHelpers.getDescription(props);
return StateTransformer.UpdateResult.Updated;
});
}
});
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.');
const { isovalues } = volume.data.sourceData.data as CubeGrid;
if (!isovalues) throw new Error('Isovalues are not computed.');
const value = isovalues[params.kind];
return createVolumeRepresentationParams(plugin, volume.data, params.directVolume ? {
type: 'direct-volume',
typeParams: {
alpha: params.alpha,
renderMode: {
name: 'isosurface',
params: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, singleLayer: false }
},
xrayShaded: params.xrayShaded
},
color: 'uniform',
colorParams: { value: params.color }
} : {
type: 'isosurface',
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
color: 'uniform',
colorParams: { value: params.color }
});
}

View File

@@ -54,13 +54,13 @@ export function computeANVIL(structure: Structure, props: ANVILProps) {
});
}
const l = StructureElement.Location.create(void 0);
const centroidHelper = new CentroidHelper();
function initialize(structure: Structure, props: ANVILProps): ANVILContext {
const l = StructureElement.Location.create(structure);
const { label_atom_id, x, y, z } = StructureProperties.atom;
const elementCount = structure.polymerResidueCount;
centroidHelper.reset();
l.structure = structure;
let offsets = new Int32Array(elementCount);
let exposed = new Array<boolean>(elementCount);
@@ -328,6 +328,7 @@ namespace HphobHphil {
const testPoint = Vec3();
export function filtered(ctx: ANVILContext, label_comp_id: StructureElement.Property<string>, filter?: (test: Vec3) => boolean): HphobHphil {
const { offsets, exposed, structure } = ctx;
const l = StructureElement.Location.create(structure);
const { x, y, z } = StructureProperties.atom;
let hphob = 0;
let hphil = 0;

View File

@@ -15,7 +15,7 @@ import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/ac
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

View File

@@ -10,7 +10,6 @@ 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,6 +26,7 @@ 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),
@@ -61,7 +61,6 @@ 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 }) })
};
@@ -91,9 +90,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) {
@@ -106,7 +109,7 @@ function getBilayerRims(ctx: RuntimeContext, data: Structure, props: BilayerRims
const builder = LinesBuilder.create(128, 64, shape?.geometry);
getLayerCircle(builder, p1, centroid, normal, scaledRadius, props);
getLayerCircle(builder, p2, centroid, normal, scaledRadius, props);
return Shape.create(name, data, builder.getLines(), () => props.color, () => props.linesSize, () => membraneLabel(data));
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>) {
@@ -142,7 +145,7 @@ function getBilayerPlanes(ctx: RuntimeContext, data: Structure, props: BilayerPl
const scaledRadius = props.radiusFactor * radius;
getLayerPlane(state, p1, centroid, normal, scaledRadius);
getLayerPlane(state, p2, centroid, normal, scaledRadius);
return Shape.create(name, data, MeshBuilder.getMesh(state), () => props.color, () => 1, () => membraneLabel(data));
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) {
@@ -151,28 +154,3 @@ function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal
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(name, 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 = new Structure(units);
for( let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
@@ -442,9 +446,9 @@ async function handleHivRna(plugin: PluginContext, packings: CellPacking[], base
async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
let file: Asset.File | undefined = undefined;
if (params.ingredients.files !== null) {
if (params.ingredients !== null) {
const fileName = `${name}.bcif`;
for (const f of params.ingredients.files) {
for (const f of params.ingredients) {
if (fileName === f.name) {
file = f;
break;
@@ -453,7 +457,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
if (!file){
// check for cif directly
const cifileName = `${name}.cif`;
for (const f of params.ingredients.files) {
for (const f of params.ingredients) {
if (cifileName === f.name) {
file = f;
break;
@@ -488,7 +492,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
}
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
const ingredientFiles = params.ingredients.files || [];
const ingredientFiles = params.ingredients || [];
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
if (params.source.name === 'id') {
@@ -563,9 +567,7 @@ const LoadCellPackModelParams = {
}, { options: [['id', 'Id'], ['file', 'File']] }),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
membrane: PD.Boolean(true),
ingredients : PD.Group({
files: PD.FileList({ accept: '.cif,.bcif,.pdb' })
}, { isExpanded: true }),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))

View File

@@ -125,7 +125,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
structures.push(s);
}
const builder = Structure.Builder({ label: name });
const builder = Structure.Builder();
let offsetInvariantId = 0;
for (const s of structures) {
let maxInvariantId = 0;

View File

@@ -55,7 +55,7 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) {
return LocationIterator(0, 1, () => NullLocation);
return LocationIterator(0, 1, 1, () => NullLocation);
}
const { locations } = prop.data;
@@ -64,7 +64,7 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
if (locations.length <= groupIndex) return NullLocation;
return locations[groupIndex];
};
return LocationIterator(locations.length, instanceCount, getLocation);
return LocationIterator(locations.length, instanceCount, 1, getLocation);
}
function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) {

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';
@@ -142,7 +142,7 @@ async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
entity: entityBuilder.getEntityTable(),
ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
model_id: Column.ofIntArray([1]),
model_name: Column.ofStringArray(['3DG Model']),
model_name: Column.ofStringArray(['G3D Model']),
}, 1),
ihm_sphere_obj_site
});
@@ -153,7 +153,8 @@ async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
haplotypes: Object.keys(data.data),
haplotype: normalized.haplotype,
resolution: data.resolution,
start: normalized.start
start: normalized.start,
chroms: normalized.chromosome,
});
return models;
@@ -174,6 +175,22 @@ export const G3dSymbols = {
const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
return info.haplotype[seqId] || '';
}
),
chromosome: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'chromosome', Type.Str),
ctx => {
if (Unit.isAtomic(ctx.element.unit)) return '';
const { asym_id } = ctx.element.unit.model.coarseHierarchy.spheres;
return asym_id.value(ctx.element.element) || '';
}
),
region: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'region', Type.Num),
ctx => {
if (Unit.isAtomic(ctx.element.unit)) return '';
const info = (G3dInfoDataProperty as any).get(ctx.element.unit.model);
if (!info) return 0;
const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
return info.start[seqId] || 0;
}
)
};
@@ -185,11 +202,31 @@ export function g3dHaplotypeQuery(haplotype: string) {
});
}
export function g3dChromosomeQuery(chr: string) {
return MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('objectPrimitive'), 'sphere']),
MS.core.rel.eq([G3dSymbols.chromosome.symbol(), chr])
])
});
}
export function g3dRegionQuery(chr: string, start: number, end: number) {
return MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('objectPrimitive'), 'sphere']),
MS.core.rel.eq([G3dSymbols.chromosome.symbol(), chr])
]),
'residue-test': MS.core.rel.inRange([G3dSymbols.region.symbol(), start, end])
});
}
export interface G3dInfoData {
haplotypes: string[],
haplotype: string[],
start: Int32Array,
resolution: number
resolution: number,
chroms: string[]
};
export const G3dLabelProvider: LociLabelProvider = {

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { debounceTime } from 'rxjs/operators';
import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { encodeMp4Animation } from './encoder';
export interface Mp4AnimationInfo {
width: number,
height: number
}
export const Mp4AnimationParams = {
quantization: PD.Numeric(18, { min: 10, max: 51 }, { description: 'Lower is better, but slower.' })
};
export class Mp4Controls extends PluginComponent {
private currentNames = new Set<string>();
private animations: PluginStateAnimation[] = [];
readonly behaviors = {
animations: this.ev.behavior<PD.Params>({ }),
current: this.ev.behavior<{ anim: PluginStateAnimation, params: PD.Params, values: any } | undefined>(void 0),
canApply: this.ev.behavior<PluginStateAnimation.CanApply>({ canApply: false }),
info: this.ev.behavior<Mp4AnimationInfo>({ width: 0, height: 0 }),
params: this.ev.behavior<PD.Values<typeof Mp4AnimationParams>>(PD.getDefaultValues(Mp4AnimationParams))
}
setCurrent(name?: string) {
const anim = this.animations.find(a => a.name === name);
if (!anim) {
this.behaviors.current.next(anim);
return;
}
const params = anim.params(this.plugin) as PD.Params;
const values = PD.getDefaultValues(params);
this.behaviors.current.next({ anim, params, values });
this.behaviors.canApply.next(anim.canApply?.(this.plugin) ?? { canApply: true });
}
setCurrentParams(values: any) {
this.behaviors.current.next({ ...this.behaviors.current.value!, values });
}
get current() {
return this.behaviors.current.value;
}
render() {
const task = Task.create('Export Animation', async ctx => {
try {
const resolution = this.plugin.helpers.viewportScreenshot?.getSizeAndViewport()!;
const anim = this.current!;
const movie = await encodeMp4Animation(this.plugin, ctx, {
animation: {
definition: anim.anim,
params: anim.values,
},
...resolution,
quantizationParameter: this.behaviors.params.value.quantization,
pass: this.plugin.helpers.viewportScreenshot?.imagePass!,
});
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
} catch (e) {
this.plugin.log.error('' + e);
throw e;
}
});
return this.plugin.runTask(task, { useOverlay: true });
}
private get manager() {
return this.plugin.managers.animation;
}
private syncInfo() {
const helper = this.plugin.helpers.viewportScreenshot;
const size = helper?.getSizeAndViewport();
if (!size) return;
this.behaviors.info.next({ width: size.viewport.width, height: size.viewport.height });
}
private sync() {
const animations = this.manager.animations.filter(a => a.isExportable);
const hasAll = animations.every(a => this.currentNames.has(a.name));
if (hasAll && this.currentNames.size === animations.length) {
return;
}
const params = {
current: PD.Select(animations[0]?.name,
animations.map(a => [a.name, a.display.name] as [string, string]),
{ label: 'Animation' })
};
const current = this.behaviors.current.value;
const hasCurrent = !!animations.find(a => a.name === current?.anim.name);
this.animations = animations;
if (!hasCurrent) {
this.setCurrent(animations[0]?.name);
}
this.behaviors.animations.next(params);
}
private init() {
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
this.sync();
});
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
this.subscribe(this.plugin.behaviors.state.isBusy, b => {
const anim = this.current;
if (!b && anim) {
this.behaviors.canApply.next(anim.anim.canApply?.(this.plugin) ?? { canApply: true });
}
});
this.sync();
this.syncInfo();
}
constructor(private plugin: PluginContext) {
super();
this.init();
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as HME from 'h264-mp4-encoder';
import { Viewport } from '../../mol-canvas3d/camera/util';
import { ImagePass } from '../../mol-canvas3d/passes/image';
import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
import { PluginContext } from '../../mol-plugin/context';
import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color';
export interface Mp4EncoderParams<A extends PluginStateAnimation = PluginStateAnimation> {
pass: ImagePass,
customBackground?: Color,
animation: PluginStateAnimation.Instance<A>,
width: number,
height: number,
viewport: Viewport,
/** default is 30 */
fps?: number,
/** Number from 10 (best quality, slowest) to 51 (worst, fastest) */
quantizationParameter?: number
}
export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin: PluginContext, ctx: RuntimeContext, params: Mp4EncoderParams<A>) {
await ctx.update({ message: 'Initializing...', isIndeterminate: true });
validateViewport(params);
const durationMs = PluginStateAnimation.getDuration(plugin, params.animation);
if (durationMs === void 0) {
throw new Error('The animation does not have the duration specified.');
}
const encoder = await HME.createH264MP4Encoder();
const { width, height } = params;
let vw = params.viewport.width, vh = params.viewport.height;
// dimensions must be a multiple of 2
if (vw % 2 !== 0) vw -= 1;
if (vh % 2 !== 0) vh -= 1;
const normalizedViewport: Viewport = { ...params.viewport, width: vw, height: vh };
encoder.width = vw;
encoder.height = vh;
if (params.quantizationParameter) encoder.quantizationParameter = params.quantizationParameter;
if (params.fps) encoder.frameRate = params.fps;
encoder.initialize();
const loop = plugin.animationLoop;
const canvasProps = plugin.canvas3d?.props;
const wasAnimating = loop.isAnimating;
let stoppedAnimation = true, finalized = false;
try {
loop.stop();
loop.resetTime(0);
if (params.customBackground !== void 0) {
plugin.canvas3d?.setProps({ renderer: { backgroundColor: params.customBackground }, transparentBackground: false }, true);
}
const fps = encoder.frameRate;
const N = Math.ceil(durationMs / 1000 * fps);
const dt = durationMs / N;
await ctx.update({ message: 'Rendering...', isIndeterminate: false, current: 0, max: N + 1 });
await plugin.managers.animation.play(params.animation.definition, params.animation.params);
stoppedAnimation = false;
for (let i = 0; i <= N; i++) {
await loop.tick(i * dt, { isSynchronous: true, manualDraw: true });
const image = params.pass.getImageData(width, height, normalizedViewport);
encoder.addFrameRgba(image.data);
if (ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
await ctx.update({ message: 'Applying finishing touches...', isIndeterminate: true });
await plugin.managers.animation.stop();
stoppedAnimation = true;
encoder.finalize();
finalized = true;
return encoder.FS.readFile(encoder.outputFilename);
} finally {
if (finalized) encoder.delete();
if (params.customBackground !== void 0) {
plugin.canvas3d?.setProps({ renderer: { backgroundColor: canvasProps?.renderer!.backgroundColor }, transparentBackground: canvasProps?.transparentBackground });
}
if (!stoppedAnimation) await plugin.managers.animation.stop();
if (wasAnimating) loop.start();
}
}
function validateViewport(params: Mp4EncoderParams) {
if (params.viewport.x + params.viewport.width > params.width || params.viewport.y + params.viewport.height > params.height) {
throw new Error('Viewport exceeds the canvas dimensions.');
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { Mp4EncoderUI } from './ui';
export const Mp4Export = PluginBehavior.create<{ }>({
name: 'extension-mp4-export',
category: 'misc',
display: {
name: 'MP4 Animation Export'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.customStructureControls.set('mp4-export', Mp4EncoderUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('mp4-export');
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @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';
import { Button } from '../../mol-plugin-ui/controls/common';
import { CameraOutlinedSvg, GetAppSvg, Icon, SubscriptionsOutlinedSvg } from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { download } from '../../mol-util/download';
import { Mp4AnimationParams, Mp4Controls } from './controls';
interface State {
busy?: boolean,
data?: { movie: Uint8Array, filename: string };
}
export class Mp4EncoderUI extends CollapsableControls<{}, State> {
private _controls: Mp4Controls | undefined;
get controls() {
return this._controls || (this._controls = new Mp4Controls(this.plugin));
}
protected defaultState(): State & CollapsableState {
return {
header: 'Export Animation',
isCollapsed: true,
brand: { accent: 'cyan', svg: SubscriptionsOutlinedSvg }
};
}
private downloadControls() {
return <>
<div className='msp-control-offset msp-help-text'>
<div className='msp-help-description' style={{ textAlign: 'center' }}>
Rendering successful!
</div>
</div>
<Button icon={GetAppSvg} onClick={this.save} style={{ marginTop: 1 }}>Save Animation</Button>
<Button onClick={() => this.setState({ data: void 0 })} style={{ marginTop: 6 }}>Clear</Button>
</>;
}
protected renderControls(): JSX.Element | null {
if (this.state.data) {
return this.downloadControls();
}
const ctrl = this.controls;
const current = ctrl.behaviors.current.value;
const info = ctrl.behaviors.info.value;
const canApply = ctrl.behaviors.canApply.value;
return <>
<ParameterControls
params={ctrl.behaviors.animations.value}
values={{ current: current?.anim.name }}
onChangeValues={xs => ctrl.setCurrent(xs.current)}
isDisabled={this.state.busy}
/>
{current && <ParameterControls
params={current.params}
values={current.values}
onChangeValues={xs => ctrl.setCurrentParams(xs)}
isDisabled={this.state.busy}
/>}
<div className='msp-control-offset msp-help-text'>
<div className='msp-help-description' style={{ textAlign: 'center' }}>
Resolution: {info.width}x{info.height}<br />
Adjust in viewport using <Icon svg={CameraOutlinedSvg} inline />
</div>
</div>
<ParameterControls
params={Mp4AnimationParams}
values={ctrl.behaviors.params.value}
onChangeValues={xs => ctrl.behaviors.params.next(xs)}
isDisabled={this.state.busy}
/>
<Button onClick={this.generate} style={{ marginTop: 1 }}
disabled={this.state.busy || !canApply.canApply}
commit={canApply.canApply ? 'on' : 'off'}>
{canApply.canApply ? 'Render' : canApply.reason ?? 'Invalid params/state'}
</Button>
</>;
}
componentDidMount() {
const merged = merge(
this.controls.behaviors.animations,
this.controls.behaviors.current,
this.controls.behaviors.canApply,
this.controls.behaviors.info,
this.controls.behaviors.params
);
this.subscribe(merged.pipe(debounceTime(10)), () => {
if (!this.state.isCollapsed) this.forceUpdate();
});
}
componentWillUnmount() {
this._controls?.dispose();
this._controls = void 0;
}
save = () => {
download(new Blob([this.state.data!.movie]), this.state.data!.filename);
}
generate = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.render();
this.setState({ busy: false, data });
} catch {
this.setState({ busy: false });
}
}
}

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];
};

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

@@ -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;
};

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) {

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 {

File diff suppressed because it is too large Load Diff

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;
};

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';
@@ -32,7 +32,7 @@ const ColorLegend = TableLegend([
]);
export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) {
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value;
const validationReport = !!ctx.structure && ctx.structure.models.length > 0 && ValidationReportProvider.get(ctx.structure.models[0]).value;
const options: [string, string][] = [];
if (validationReport) {
const kinds = new Set<string>();
@@ -48,7 +48,7 @@ export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQua
export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> {
let color: LocationColor = () => DefaultColor;
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
const validationReport = !!ctx.structure && ctx.structure.models.length > 0 ? ValidationReportProvider.get(ctx.structure.models[0]) : void 0;
const contextHash = validationReport?.version;
const value = validationReport?.value;
@@ -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;
};

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;
};

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

@@ -143,7 +143,7 @@ function createIntraClashIterator(structureGroup: StructureGroup): LocationItera
location.element = unit.elements[a[groupIndex]];
return location;
};
return LocationIterator(groupCount, instanceCount, getLocation);
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
//
@@ -255,7 +255,7 @@ function createInterClashIterator(structure: Structure): LocationIterator {
location.element = location.unit.elements[clash.indexA];
return location;
};
return LocationIterator(groupCount, instanceCount, getLocation, true);
return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
}
//

View File

@@ -10,22 +10,37 @@ import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
export { Camera };
export { ICamera, Camera };
class Camera {
interface ICamera {
readonly viewport: Viewport,
readonly view: Mat4,
readonly projection: Mat4,
readonly projectionView: Mat4,
readonly inverseProjectionView: Mat4,
readonly state: Readonly<Camera.Snapshot>,
readonly viewOffset: Camera.ViewOffset,
readonly far: number,
readonly near: number,
readonly fogFar: number,
readonly fogNear: number,
}
class Camera implements ICamera {
readonly view: Mat4 = Mat4.identity();
readonly projection: Mat4 = Mat4.identity();
readonly projectionView: Mat4 = Mat4.identity();
readonly inverseProjectionView: Mat4 = Mat4.identity();
private pixelScale: number
get pixelRatio () {
const dpr = (typeof window !== 'undefined') ? window.devicePixelRatio : 1;
return dpr * this.pixelScale;
}
readonly viewport: Viewport;
readonly state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
readonly viewOffset: Camera.ViewOffset = {
enabled: false,
fullWidth: 1, fullHeight: 1,
offsetX: 0, offsetY: 0,
width: 1, height: 1
}
readonly viewOffset = Camera.ViewOffset();
near = 1
far = 10000
@@ -52,6 +67,10 @@ class Camera {
update() {
const snapshot = this.state as Camera.Snapshot;
if (snapshot.radiusMax === 0) {
return false;
}
const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target);
this.zoom = this.viewport.height / height;
@@ -86,12 +105,7 @@ class Camera {
}
getTargetDistance(radius: number) {
const r = Math.max(radius, 0.01);
const { fov } = this.state;
const { width, height } = this.viewport;
const aspect = width / height;
const aspectFactor = (height < width ? 1 : aspect);
return Math.abs((r / aspectFactor) / Math.sin(fov / 2));
return Camera.targetDistance(radius, this.state.fov, this.viewport.width, this.viewport.height);
}
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
@@ -126,8 +140,9 @@ class Camera {
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView);
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(0, 0, 128, 128), props: Partial<{ pixelScale: number }> = {}) {
this.viewport = viewport;
this.pixelScale = props.pixelScale || 1;
Camera.copySnapshot(this.state, state);
}
}
@@ -150,6 +165,15 @@ namespace Camera {
height: number
}
export function ViewOffset(): ViewOffset {
return {
enabled: false,
fullWidth: 1, fullHeight: 1,
offsetX: 0, offsetY: 0,
width: 1, height: 1
};
}
export function setViewOffset(out: ViewOffset, fullWidth: number, fullHeight: number, offsetX: number, offsetY: number, width: number, height: number) {
out.fullWidth = fullWidth;
out.fullHeight = fullHeight;
@@ -159,6 +183,23 @@ namespace Camera {
out.height = height;
}
export function copyViewOffset(out: ViewOffset, view: ViewOffset) {
out.enabled = view.enabled;
out.fullWidth = view.fullWidth;
out.fullHeight = view.fullHeight;
out.offsetX = view.offsetX;
out.offsetY = view.offsetY;
out.width = view.width;
out.height = view.height;
}
export function targetDistance(radius: number, fov: number, width: number, height: number) {
const r = Math.max(radius, 0.01);
const aspect = width / height;
const aspectFactor = (height < width ? 1 : aspect);
return Math.abs((r / aspectFactor) / Math.sin(fov / 2));
}
export function createDefaultSnapshot(): Snapshot {
return {
mode: 'perspective',
@@ -168,8 +209,8 @@ namespace Camera {
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
radius: 10,
radiusMax: 10,
radius: 0,
radiusMax: 0,
fog: 50,
clipFar: true
};
@@ -211,10 +252,10 @@ namespace Camera {
function updateOrtho(camera: Camera) {
const { viewport, zoom, near, far, viewOffset } = camera;
const fullLeft = -(viewport.width - viewport.x) / 2;
const fullRight = (viewport.width - viewport.x) / 2;
const fullTop = (viewport.height - viewport.y) / 2;
const fullBottom = -(viewport.height - viewport.y) / 2;
const fullLeft = -viewport.width / 2;
const fullRight = viewport.width / 2;
const fullTop = viewport.height / 2;
const fullBottom = -viewport.height / 2;
const dx = (fullRight - fullLeft) / (2 * zoom);
const dy = (fullTop - fullBottom) / (2 * zoom);
@@ -278,12 +319,12 @@ 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(5, near);
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
} else {
near = Math.max(0, near);
@@ -296,7 +337,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

@@ -0,0 +1,141 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Adapted from three.js, The MIT License, Copyright © 2010-2020 three.js authors
*/
import { Mat4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera, ICamera } from '../camera';
import { Viewport } from './util';
export const StereoCameraParams = {
eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
};
export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
export type StereoCameraProps = PD.Values<typeof StereoCameraParams>
export { StereoCamera };
class StereoCamera {
readonly left: ICamera = new EyeCamera();
readonly right: ICamera = new EyeCamera();
get viewport() {
return this.parent.viewport;
}
get viewOffset() {
return this.parent.viewOffset;
}
private props: StereoCameraProps
constructor(private parent: Camera, props: Partial<StereoCameraProps> = {}) {
this.props = { ...DefaultStereoCameraProps, ...props };
}
setProps(props: Partial<StereoCameraProps>) {
Object.assign(this.props, props);
}
update() {
this.parent.update();
update(this.parent, this.props, this.left as EyeCamera, this.right as EyeCamera);
}
}
namespace StereoCamera {
export function is(camera: Camera | StereoCamera): camera is StereoCamera {
return 'left' in camera && 'right' in camera;
}
}
class EyeCamera implements ICamera {
viewport = Viewport.create(0, 0, 0, 0);
view = Mat4();
projection = Mat4();
projectionView = Mat4();
inverseProjectionView = Mat4();
state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
viewOffset: Readonly<Camera.ViewOffset> = Camera.ViewOffset();
far: number = 0;
near: number = 0;
fogFar: number = 0;
fogNear: number = 0;
}
const eyeLeft = Mat4.identity(), eyeRight = Mat4.identity();
function update(camera: Camera, props: StereoCameraProps, left: EyeCamera, right: EyeCamera) {
// Copy the states
Viewport.copy(left.viewport, camera.viewport);
Mat4.copy(left.view, camera.view);
Mat4.copy(left.projection, camera.projection);
Camera.copySnapshot(left.state, camera.state);
Camera.copyViewOffset(left.viewOffset, camera.viewOffset);
left.far = camera.far;
left.near = camera.near;
left.fogFar = camera.fogFar;
left.fogNear = camera.fogNear;
Viewport.copy(right.viewport, camera.viewport);
Mat4.copy(right.view, camera.view);
Mat4.copy(right.projection, camera.projection);
Camera.copySnapshot(right.state, camera.state);
Camera.copyViewOffset(right.viewOffset, camera.viewOffset);
right.far = camera.far;
right.near = camera.near;
right.fogFar = camera.fogFar;
right.fogNear = camera.fogNear;
// update the view offsets
const w = Math.floor(camera.viewport.width / 2);
const aspect = w / camera.viewport.height;
left.viewport.width = w;
right.viewport.x += w;
right.viewport.width -= w;
// update the projection and view matrices
const eyeSepHalf = props.eyeSeparation / 2;
const eyeSepOnProjection = eyeSepHalf * camera.near / props.focus;
const ymax = camera.near * Math.tan(camera.state.fov * 0.5);
let xmin, xmax;
// translate xOffset
eyeLeft[12] = -eyeSepHalf;
eyeRight[12] = eyeSepHalf;
// for left eye
xmin = -ymax * aspect + eyeSepOnProjection;
xmax = ymax * aspect + eyeSepOnProjection;
left.projection[0] = 2 * camera.near / (xmax - xmin);
left.projection[8] = (xmax + xmin) / (xmax - xmin);
Mat4.mul(left.view, left.view, eyeLeft);
Mat4.mul(left.projectionView, left.projection, left.view);
Mat4.invert(left.inverseProjectionView, left.projectionView);
// for right eye
xmin = -ymax * aspect - eyeSepOnProjection;
xmax = ymax * aspect - eyeSepOnProjection;
right.projection[0] = 2 * camera.near / (xmax - xmin);
right.projection[8] = (xmax + xmin) / (xmax - xmin);
Mat4.mul(right.view, right.view, eyeRight);
Mat4.mul(right.projectionView, right.projection, right.view);
Mat4.invert(right.inverseProjectionView, right.projectionView);
}

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,44 +8,50 @@
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 { GraphicsRenderVariant } from '../mol-gl/webgl/render-item';
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';
import { Camera } from './camera';
import { ParamDefinition as PD } from '../mol-util/param-definition';
import { BoundingSphereHelper, DebugHelperParams } from './helper/bounding-sphere-helper';
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 { MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PixelData } from '../mol-util/image';
import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { PostprocessingParams } from './passes/postprocessing';
import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PickData } from './passes/pick';
import { PickHelper } from './passes/pick';
import { ImagePass, ImageProps } from './passes/image';
import { Sphere3D } from '../mol-math/geometry';
import { isDebugMode } from '../mol-util/debug';
import { CameraHelperParams } from './helper/camera-helper';
import { produce } from 'immer';
import { HandleHelper, HandleHelperParams } from './helper/handle-helper';
import { HandleHelperParams } from './helper/handle-helper';
import { StereoCamera, StereoCameraParams } from './camera/stereo';
import { Helper } from './helper/helper';
import { Passes } from './passes/passes';
import { shallowEqual } from '../mol-util';
export const Canvas3DParams = {
camera: PD.Group({
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
helper: PD.Group(CameraHelperParams, { isFlat: true })
mode: PD.Select('perspective', PD.arrayToOptions(['perspective', 'orthographic'] as const), { label: 'Camera' }),
helper: PD.Group(CameraHelperParams, { isFlat: true }),
stereo: PD.MappedStatic('off', {
on: PD.Group(StereoCameraParams),
off: PD.Group({})
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
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' }),
@@ -53,6 +59,15 @@ export const Canvas3DParams = {
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
}, { pivot: 'radius' }),
viewport: PD.MappedStatic('canvas', {
canvas: PD.Group({}),
custom: PD.Group({
x: PD.Numeric(0),
y: PD.Numeric(0),
width: PD.Numeric(128),
height: PD.Numeric(128)
})
}),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
transparentBackground: PD.Boolean(false),
@@ -66,72 +81,57 @@ export const Canvas3DParams = {
};
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
export type PartialCanvas3DProps = { [K in keyof Canvas3DProps]?: Partial<Canvas3DProps[K]> }
export { Canvas3D };
interface Canvas3D {
readonly webgl: WebGLContext,
add(repr: Representation.Any): void
remove(repr: Representation.Any): void
/**
* This function must be called if animate() is not set up so that add/remove actions take place.
*/
commit(isSynchronous?: boolean): void
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
clear(): void
syncVisibility(): void
requestDraw(force?: boolean): void
animate(): void
identify(x: number, y: number): PickingId | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId): Representation.Loci
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
handleResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
getPixelData(variant: GraphicsRenderVariant): PixelData
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void
getImagePass(props: Partial<ImageProps>): ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
readonly input: InputObserver
readonly stats: RendererStats
readonly interaction: Canvas3dInteractionHelper['events']
dispose(): void
export type PartialCanvas3DProps = {
[K in keyof Canvas3DProps]?: Canvas3DProps[K] extends { name: string, params: any } ? Canvas3DProps[K] : Partial<Canvas3DProps[K]>
}
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()));
export { Canvas3DContext };
namespace Canvas3D {
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
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 }
/** 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
}
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
namespace Canvas3DContext {
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, {
alpha: true,
antialias: true,
depth: true,
preserveDrawingBuffer: true,
premultipliedAlpha: false,
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);
const webgl = createContext(gl);
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) {
// 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;
@@ -150,35 +150,130 @@ namespace Canvas3D {
// https://www.khronos.org/webgl/wiki/HandlingContextLost
canvas.addEventListener('webglcontextlost', e => {
const contextLost = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const handleWebglContextLost = (e: Event) => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
}, false);
contextLost.next(now());
};
canvas.addEventListener('webglcontextrestored', () => {
const handlewWebglContextRestored = () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored();
webgl.handleContextRestored(() => {
passes.draw.reset();
});
if (isDebugMode) console.log('context restored');
}, false);
};
return Canvas3D.create(webgl, input, props);
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 function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p = { ...DefaultCanvas3DParams, ...props };
export { Canvas3D };
interface Canvas3D {
readonly webgl: WebGLContext,
add(repr: Representation.Any): void
remove(repr: Representation.Any): void
/**
* This function must be called if animate() is not set up so that add/remove actions take place.
*/
commit(isSynchronous?: boolean): void
/**
* Funcion for external "animation" control
* Calls commit.
*/
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
clear(): void
syncVisibility(): void
requestDraw(force?: boolean): void
/** Reset the timers, used by "animate" */
resetTime(t: number): void
animate(): void
pause(): 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
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
getImagePass(props: Partial<ImageProps>): ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
readonly input: InputObserver
readonly stats: RendererStats
readonly interaction: Canvas3dInteractionHelper['events']
dispose(): void
}
const requestAnimationFrame = typeof window !== 'undefined'
? window.requestAnimationFrame
: (f: (time: number) => void) => setImmediate(() => f(Date.now())) as unknown as number;
const cancelAnimationFrame = typeof window !== 'undefined'
? window.cancelAnimationFrame
: (handle: number) => clearImmediate(handle as unknown as NodeJS.Immediate);
namespace Canvas3D {
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
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 create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
const reprCount = new BehaviorSubject(0);
const startTime = now();
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;
let width = gl.drawingBufferWidth;
let height = gl.drawingBufferHeight;
let x = 0;
let y = 0;
let width = 128;
let height = 128;
updateViewport();
const scene = Scene.create(webgl);
@@ -187,40 +282,41 @@ namespace Canvas3D {
mode: p.camera.mode,
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
clipFar: p.cameraClipping.far
});
}, { x, y, width, height }, { pixelScale: attribs.pixelScale });
const stereoCamera = new StereoCamera(camera, p.camera.stereo.params);
const controls = TrackballControls.create(input, camera, p.trackball);
const renderer = Renderer.create(webgl, p.renderer);
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
const handleHelper = new HandleHelper(webgl, p.handle);
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
const helper = new Helper(webgl, scene, p);
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, handleHelper, {
cameraHelper: p.camera.helper
});
const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.5);
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height });
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 resizeRequested = false;
function getLoci(pickingId: PickingId) {
let notifyDidDraw = true;
function getLoci(pickingId: PickingId | undefined) {
let loci: Loci = EmptyLoci;
let repr: Representation.Any = Representation.Empty;
loci = handleHelper.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
if (!isEmptyLoci(loci)) {
console.warn('found another loci, this should not happen');
if (pickingId) {
loci = helper.handle.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
if (!isEmptyLoci(loci)) {
console.warn('found another loci, this should not happen');
}
loci = _loci;
repr = _repr;
}
loci = _loci;
repr = _repr;
}
});
});
}
return { loci, repr };
}
@@ -230,36 +326,50 @@ namespace Canvas3D {
if (repr) {
changed = repr.mark(loci, action);
} else {
changed = handleHelper.mark(loci, action);
changed = helper.handle.mark(loci, action);
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed) {
scene.update(void 0, true);
handleHelper.scene.update(void 0, true);
const prevPickDirty = pickPass.pickDirty;
helper.handle.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw(true);
pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
}
}
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;
let didRender = false;
controls.update(currentTime);
Viewport.set(camera.viewport, 0, 0, width, height);
const cameraChanged = camera.update();
multiSample.update(force || cameraChanged, currentTime);
const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
if (force || cameraChanged || multiSample.enabled) {
renderer.setViewport(0, 0, width, height);
if (multiSample.enabled) {
multiSample.render(true, p.transparentBackground);
} else {
drawPass.render(!postprocessing.enabled, p.transparentBackground);
if (postprocessing.enabled) postprocessing.render(true);
if (resized || force || cameraChanged || multiSampleChanged) {
let cam: Camera | StereoCamera = camera;
if (p.camera.stereo.name === 'on') {
stereoCamera.update();
cam = stereoCamera;
}
pickPass.pickDirty = true;
if (MultiSamplePass.isEnabled(p.multiSample)) {
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
} else {
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing);
}
pickHelper.dirty = true;
didRender = true;
}
@@ -271,7 +381,7 @@ namespace Canvas3D {
let currentTime = 0;
function draw(force?: boolean) {
if (render(!!force || forceNextDraw)) {
if (render(!!force || forceNextDraw) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
forceNextDraw = false;
@@ -284,20 +394,46 @@ namespace Canvas3D {
forceNextDraw = !!force;
}
function animate() {
currentTime = now();
commit();
let animationFrameHandle = 0;
function tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }) {
currentTime = t;
commit(options?.isSynchronous);
camera.transition.tick(currentTime);
if (options?.manualDraw) {
return;
}
draw(false);
if (!camera.transition.inTransition && !webgl.isContextLost) {
interactionHelper.tick(currentTime);
}
requestAnimationFrame(animate);
}
function identify(x: number, y: number): PickingId | undefined {
return webgl.isContextLost ? undefined : pickPass.identify(x, y);
function _animate() {
tick(now());
animationFrameHandle = requestAnimationFrame(_animate);
}
function resetTime(t: now.Timestamp) {
startTime = t;
controls.start(t);
}
function animate() {
controls.start(now());
if (animationFrameHandle === 0) _animate();
}
function pause() {
cancelAnimationFrame(animationFrameHandle);
animationFrameHandle = 0;
}
function identify(x: number, y: number): PickData | undefined {
const cam = p.camera.stereo.name === 'on' ? stereoCamera : camera;
return webgl.isContextLost ? undefined : pickHelper.identify(x, y, cam);
}
function commit(isSynchronous: boolean = false) {
@@ -306,10 +442,11 @@ namespace Canvas3D {
if (allCommited) {
resolveCameraReset();
if (forceDrawAfterAllCommited) {
if (debugHelper.isEnabled) debugHelper.update();
if (helper.debug.isEnabled) helper.debug.update();
draw(true);
forceDrawAfterAllCommited = false;
}
commited.next(now());
}
}
@@ -321,7 +458,7 @@ namespace Canvas3D {
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
const focus = camera.getFocus(center, radius);
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
camera.setState(snapshot, duration);
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
}
nextCameraResetDuration = void 0;
@@ -348,7 +485,8 @@ namespace Canvas3D {
const b = r.values.boundingSphere.ref.value;
if (!b.radius) continue;
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
const cameraDist = Vec3.distance(cameraSphere.center, b.center);
if ((cameraDist > cameraSphere.radius || cameraDist > b.radius || b.radius > camera.state.radiusMax) && !Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
}
@@ -364,8 +502,8 @@ namespace Canvas3D {
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
if (debugHelper.isEnabled) debugHelper.update();
if (reprCount.value === 0 || shouldResetCamera()) {
if (helper.debug.isEnabled) helper.debug.update();
if (!p.camera.manualReset && (reprCount.value === 0 || shouldResetCamera())) {
cameraResetRequested = true;
}
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
@@ -382,7 +520,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) {
@@ -414,7 +561,6 @@ namespace Canvas3D {
if (renderObjects) {
renderObjects.forEach(o => scene.remove(o));
reprRenderObjects.delete(repr);
scene.update(repr.renderObjects, false, true);
forceDrawAfterAllCommited = true;
if (isDebugMode) consoleStats();
}
@@ -444,7 +590,9 @@ namespace Canvas3D {
return {
camera: {
mode: camera.state.mode,
helper: { ...drawPass.props.cameraHelper }
helper: { ...helper.camera.props },
stereo: { ...p.camera.stereo },
manualReset: !!p.camera.manualReset
},
cameraFog: camera.state.fog > 0
? { name: 'on' as const, params: { intensity: camera.state.fog } }
@@ -452,23 +600,36 @@ namespace Canvas3D {
cameraClipping: { far: camera.state.clipFar, radius },
cameraResetDurationMs: p.cameraResetDurationMs,
transparentBackground: p.transparentBackground,
viewport: p.viewport,
postprocessing: { ...postprocessing.props },
multiSample: { ...multiSample.props },
postprocessing: { ...p.postprocessing },
multiSample: { ...p.multiSample },
renderer: { ...renderer.props },
trackball: { ...controls.props },
debug: { ...debugHelper.props },
handle: { ...handleHelper.props },
debug: { ...helper.debug.props },
handle: { ...helper.handle.props },
};
}
handleResize();
const contextRestoredSub = contextRestored.subscribe(() => {
pickPass.pickDirty = true;
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,
@@ -489,7 +650,7 @@ namespace Canvas3D {
reprUpdatedSubscriptions.clear();
reprRenderObjects.clear();
scene.clear();
debugHelper.clear();
helper.debug.clear();
requestDraw(true);
reprCount.next(reprRenderObjects.size);
},
@@ -500,19 +661,24 @@ namespace Canvas3D {
}
if (scene.syncVisibility()) {
if (debugHelper.isEnabled) debugHelper.update();
if (helper.debug.isEnabled) helper.debug.update();
}
requestDraw(true);
},
// draw,
requestDraw,
tick,
animate,
resetTime,
pause,
identify,
mark,
getLoci,
handleResize,
requestResize: () => {
resizeRequested = true;
},
requestCameraReset: options => {
nextCameraResetDuration = options?.durationMs;
nextCameraResetSnapshot = options?.snapshot;
@@ -520,18 +686,13 @@ namespace Canvas3D {
},
camera,
boundingSphere: scene.boundingSphere,
getPixelData: (variant: GraphicsRenderVariant) => {
switch (variant) {
case 'color': return webgl.getDrawingBufferPixelData();
case 'pickObject': return pickPass.objectPickTarget.getPixelData();
case 'pickInstance': return pickPass.instancePickTarget.getPixelData();
case 'pickGroup': return pickPass.groupPickTarget.getPixelData();
case 'depth': return readTexture(webgl, drawPass.depthTexture) as PixelData;
}
},
get notifyDidDraw() { return notifyDidDraw; },
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
commited,
reprCount,
setProps: (properties) => {
resized,
setProps: (properties, doNotRequestDraw = false) => {
const props: PartialCanvas3DProps = typeof properties === 'function'
? produce(getProps(), properties)
: properties;
@@ -558,47 +719,43 @@ namespace Canvas3D {
}
if (Object.keys(cameraState).length > 0) camera.setState(cameraState);
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper });
if (props.camera?.helper) helper.camera.setProps(props.camera.helper);
if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset;
if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo);
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
if (props.viewport !== undefined) {
const doNotUpdate = p.viewport === props.viewport ||
(p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params));
if (props.postprocessing) postprocessing.setProps(props.postprocessing);
if (props.multiSample) multiSample.setProps(props.multiSample);
if (!doNotUpdate) {
p.viewport = props.viewport;
updateViewport();
syncViewport();
}
}
if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing);
if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
if (props.renderer) renderer.setProps(props.renderer);
if (props.trackball) controls.setProps(props.trackball);
if (props.debug) debugHelper.setProps(props.debug);
if (props.handle) handleHelper.setProps(props.handle);
if (props.debug) helper.debug.setProps(props.debug);
if (props.handle) helper.handle.setProps(props.handle);
requestDraw(true);
if (cameraState.mode === 'orthographic') {
p.camera.stereo.name = 'off';
}
if (!doNotRequestDraw) {
requestDraw(true);
}
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, debugHelper, handleHelper, props);
return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
},
get props() {
const radius = scene.boundingSphere.radius > 0
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
: 0;
return {
camera: {
mode: camera.state.mode,
helper: { ...drawPass.props.cameraHelper }
},
cameraFog: camera.state.fog > 0
? { name: 'on' as const, params: { intensity: camera.state.fog } }
: { name: 'off' as const, params: {} },
cameraClipping: { far: camera.state.clipFar, radius },
cameraResetDurationMs: p.cameraResetDurationMs,
transparentBackground: p.transparentBackground,
postprocessing: { ...postprocessing.props },
multiSample: { ...multiSample.props },
renderer: { ...renderer.props },
trackball: { ...controls.props },
debug: { ...debugHelper.props },
handle: { ...handleHelper.props },
};
return getProps();
},
get input() {
return input;
@@ -613,28 +770,32 @@ namespace Canvas3D {
contextRestoredSub.unsubscribe();
scene.clear();
debugHelper.clear();
input.dispose();
helper.debug.clear();
controls.dispose();
renderer.dispose();
interactionHelper.dispose();
}
};
function handleResize() {
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
function updateViewport() {
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
} else {
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;
}
}
renderer.setViewport(0, 0, width, height);
Viewport.set(camera.viewport, 0, 0, width, height);
Viewport.set(controls.viewport, 0, 0, width, height);
drawPass.setSize(width, height);
pickPass.setSize(width, height);
postprocessing.setSize(width, height);
multiSample.setSize(width, height);
requestDraw(true);
function syncViewport() {
pickHelper.setViewport(x, y, width, height);
renderer.setViewport(x, y, width, height);
Viewport.set(camera.viewport, x, y, width, height);
Viewport.set(controls.viewport, x, y, width, height);
}
}
}

View File

@@ -44,9 +44,8 @@ export namespace ObjectControls {
const height = 2 * Math.tan(camera.state.fov / 2) * dist;
const zoom = camera.viewport.height / height;
const dpr = window.devicePixelRatio;
panMouseChange[0] *= (1 / zoom) * camera.viewport.width * dpr;
panMouseChange[1] *= (1 / zoom) * camera.viewport.height * dpr;
panMouseChange[0] *= (1 / zoom) * camera.viewport.width * camera.pixelRatio;
panMouseChange[1] *= (1 / zoom) * camera.viewport.height * camera.pixelRatio;
Vec3.cross(panOffset, Vec3.copy(panOffset, eye), camera.up);
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]);

View File

@@ -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 } 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' }),
@@ -60,6 +60,7 @@ interface TrackballControls {
readonly props: Readonly<TrackballControlsProps>
setProps: (props: Partial<TrackballControlsProps>) => void
start: (t: number) => void
update: (t: number) => void
reset: () => void
dispose: () => void
@@ -68,7 +69,7 @@ namespace TrackballControls {
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props };
const viewport = Viewport();
const viewport = Viewport.clone(camera.viewport);
let disposed = false;
@@ -137,7 +138,8 @@ namespace TrackballControls {
const dy = _rotCurr[1] - _rotPrev[1];
Vec3.set(rotMoveDir, dx, dy, 0);
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed;
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);
@@ -227,7 +229,7 @@ namespace TrackballControls {
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart);
if (Vec2.squaredMagnitude(panMouseChange)) {
const factor = window.devicePixelRatio * p.panSpeed;
const factor = input.pixelRatio * p.panSpeed;
panMouseChange[0] *= (1 / camera.zoom) * camera.viewport.width * factor;
panMouseChange[1] *= (1 / camera.zoom) * camera.viewport.height * factor;
@@ -271,11 +273,22 @@ namespace TrackballControls {
}
}
function outsideViewport(x: number, y: number) {
x *= input.pixelRatio;
y *= input.pixelRatio;
return (
x > viewport.x + viewport.width ||
input.height - y > viewport.y + viewport.height ||
x < viewport.x ||
input.height - y < viewport.y
);
}
let lastUpdated = -1;
/** Update the object's position, direction and up vectors */
function update(t: number) {
if (lastUpdated === t) return;
if (p.spin) spin(t - lastUpdated);
if (p.spin && lastUpdated > 0) spin(t - lastUpdated);
Vec3.sub(_eye, camera.position, camera.target);
@@ -307,7 +320,12 @@ namespace TrackballControls {
// listeners
function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
function onDrag({ x, y, pageX, pageY, buttons, modifiers, isStart }: DragInput) {
const isOutside = outsideViewport(x, y);
if (isStart && isOutside) return;
if (!isStart && !_isInteracting) return;
_isInteracting = true;
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
@@ -358,7 +376,9 @@ namespace TrackballControls {
_isInteracting = false;
}
function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) {
function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
if (outsideViewport(x, y)) return;
const delta = absMax(dx, dy, dz);
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001;
@@ -387,13 +407,17 @@ namespace TrackballControls {
const _spinSpeed = Vec2.create(0.005, 0);
function spin(deltaT: number) {
if (p.spinSpeed === 0) return;
const frameSpeed = (p.spinSpeed || 0) / 1000;
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
}
// force an update at start
update(0);
function start(t: number) {
lastUpdated = -1;
update(t);
}
return {
viewport,
@@ -403,6 +427,7 @@ namespace TrackballControls {
Object.assign(p, props);
},
start,
update,
reset,
dispose

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';
@@ -67,6 +67,7 @@ export class BoundingSphereHelper {
uInstanceCount: ro.values.uInstanceCount,
instanceCount: ro.values.instanceCount,
aInstance: ro.values.aInstance,
hasReflection: ro.values.hasReflection,
});
if (newInstanceData) this.instancesData.set(ro, newInstanceData);
});
@@ -131,7 +132,7 @@ function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data:
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh);
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform);
if (data) {
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh));
ValueCell.updateIfChanged(renderObject.values.drawCount, Geometry.getDrawCount(mesh));
} else {
scene.add(renderObject);
}
@@ -159,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, 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

@@ -5,8 +5,8 @@
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import Scene from '../../mol-gl/scene';
import { Camera } from '../camera';
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';
@@ -15,7 +15,6 @@ import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ColorNames } from '../../mol-util/color/names';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Viewport } from '../camera/util';
import { ValueCell } from '../../mol-util';
import { Sphere3D } from '../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import produce from 'immer';
@@ -71,6 +70,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();
@@ -87,43 +87,47 @@ export class CameraHelper {
return this.props.axes.name === 'on';
}
update(camera: Camera) {
update(camera: ICamera) {
if (!this.renderObject) return;
updateCamera(this.camera, camera.viewport);
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4;
Mat4.extractRotation(m, camera.view);
updateCamera(this.camera, camera.viewport, camera.viewOffset);
Mat4.extractRotation(this.scene.view, camera.view);
const r = this.renderObject.values.boundingSphere.ref.value.radius;
Mat4.setTranslation(m, Vec3.create(
Mat4.setTranslation(this.scene.view, Vec3.create(
-camera.viewport.width / 2 + r,
-camera.viewport.height / 2 + r,
0
));
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
this.scene.update([this.renderObject], true);
}
}
function updateCamera(camera: Camera, viewport: Viewport) {
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
const { near, far } = camera;
const fullLeft = -(viewport.width - viewport.x) / 2;
const fullRight = (viewport.width - viewport.x) / 2;
const fullTop = (viewport.height - viewport.y) / 2;
const fullBottom = -(viewport.height - viewport.y) / 2;
const fullLeft = -viewport.width / 2;
const fullRight = viewport.width / 2;
const fullTop = viewport.height / 2;
const fullBottom = -viewport.height / 2;
const dx = (fullRight - fullLeft) / 2;
const dy = (fullTop - fullBottom) / 2;
const cx = (fullRight + fullLeft) / 2;
const cy = (fullTop + fullBottom) / 2;
const left = cx - dx;
const right = cx + dx;
const top = cy + dy;
const bottom = cy - dy;
let left = cx - dx;
let right = cx + dx;
let top = cy + dy;
let bottom = cy - dy;
if (viewOffset.enabled) {
const scaleW = (fullRight - fullLeft) / viewOffset.width;
const scaleH = (fullTop - fullBottom) / viewOffset.height;
left += scaleW * viewOffset.offsetX;
right = left + scaleW * viewOffset.width;
top -= scaleH * viewOffset.offsetY;
bottom = top - scaleH * viewOffset.height;
}
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
}

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

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
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';
import { CameraHelper, CameraHelperParams } from './camera-helper';
import { HandleHelper, HandleHelperParams } from './handle-helper';
export const HelperParams = {
debug: PD.Group(DebugHelperParams),
camera: PD.Group({
helper: PD.Group(CameraHelperParams)
}),
handle: PD.Group(HandleHelperParams),
};
export const DefaultHelperProps = PD.getDefaultValues(HelperParams);
export type HelperProps = PD.Values<typeof HelperParams>
export class Helper {
readonly debug: BoundingSphereHelper
readonly camera: CameraHelper
readonly handle: HandleHelper
constructor(webgl: WebGLContext, scene: Scene, props: Partial<HelperProps> = {}) {
const p = { ...DefaultHelperProps, ...props };
this.debug = new BoundingSphereHelper(webgl, scene, p.debug);
this.camera = new CameraHelper(webgl, p.camera.helper);
this.handle = new HandleHelper(webgl, p.handle);
}
}

View File

@@ -7,9 +7,10 @@
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 } from '../../mol-math/linear-algebra';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Camera } from '../camera';
type Canvas3D = import('../canvas3d').Canvas3D
type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
@@ -33,6 +34,7 @@ export class Canvas3dInteractionHelper {
private endY = -1;
private id: PickingId | undefined = void 0;
private position: Vec3 | undefined = void 0;
private currentIdentifyT = 0;
private isInteracting = false;
@@ -60,33 +62,25 @@ export class Canvas3dInteractionHelper {
}
if (xyChanged) {
this.id = this.canvasIdentify(this.endX, this.endY);
const pickData = this.canvasIdentify(this.endX, this.endY);
this.id = pickData?.id;
this.position = pickData?.position;
this.startX = this.endX;
this.startY = this.endY;
}
if (!this.id) {
this.prevLoci = Representation.Loci.Empty;
return;
}
if (e === InputEvent.Click) {
const loci = this.getLoci(this.id);
this.events.click.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers });
this.events.click.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
this.prevLoci = loci;
return;
}
if (!this.inside || this.currentIdentifyT !== t) {
return;
}
if (!this.inside || this.currentIdentifyT !== t || !xyChanged || this.outsideViewport(this.endX, this.endY)) return;
const loci = this.getLoci(this.id);
// only broadcast the latest hover
if (!Representation.Loci.areEqual(this.prevLoci, loci)) {
this.events.hover.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers });
this.prevLoci = loci;
}
this.events.hover.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
this.prevLoci = loci;
}
tick(t: number) {
@@ -97,15 +91,15 @@ export class Canvas3dInteractionHelper {
}
}
leave() {
private leave() {
this.inside = false;
if (Representation.Loci.isEmpty(this.prevLoci)) {
if (!Representation.Loci.isEmpty(this.prevLoci)) {
this.prevLoci = Representation.Loci.Empty;
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, button: this.button, modifiers: this.modifiers });
}
}
move(x: number, y: number, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
private move(x: number, y: number, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
this.inside = true;
this.buttons = buttons;
this.button = button;
@@ -114,7 +108,7 @@ export class Canvas3dInteractionHelper {
this.endY = y;
}
click(x: number, y: number, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
private click(x: number, y: number, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
this.endX = x;
this.endY = y;
this.buttons = buttons;
@@ -123,7 +117,7 @@ export class Canvas3dInteractionHelper {
this.identify(InputEvent.Click, 0);
}
drag(x: number, y: number, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
private drag(x: number, y: number, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
this.endX = x;
this.endY = y;
this.buttons = buttons;
@@ -132,17 +126,29 @@ export class Canvas3dInteractionHelper {
this.identify(InputEvent.Drag, 0);
}
modify(modifiers: ModifiersKeys) {
if (Representation.Loci.isEmpty(this.prevLoci) || ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
private modify(modifiers: ModifiersKeys) {
if (ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
this.modifiers = modifiers;
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, button: this.button, modifiers: this.modifiers });
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
}
private outsideViewport(x: number, y: number) {
const { input, camera: { viewport } } = this;
x *= input.pixelRatio;
y *= input.pixelRatio;
return (
x > viewport.x + viewport.width ||
input.height - y > viewport.y + viewport.height ||
x < viewport.x ||
input.height - y < viewport.y
);
}
dispose() {
this.ev.dispose();
}
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 30) {
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, private maxFps: number = 30) {
input.drag.subscribe(({x, y, buttons, button, modifiers }) => {
this.isInteracting = true;
// console.log('drag');
@@ -161,6 +167,7 @@ export class Canvas3dInteractionHelper {
});
input.click.subscribe(({x, y, buttons, button, modifiers }) => {
if (this.outsideViewport(x, y)) return;
// console.log('click');
this.click(x, y, buttons, button, modifiers);
});
@@ -170,6 +177,9 @@ export class Canvas3dInteractionHelper {
this.isInteracting = false;
});
input.modifiers.subscribe(modifiers => this.modify(modifiers));
input.modifiers.subscribe(modifiers => {
// console.log('modifiers');
this.modify(modifiers);
});
}
}

View File

@@ -1,107 +1,356 @@
/**
* 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 { WebGLContext } from '../../mol-gl/webgl/context';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { createNullRenderTarget, RenderTarget } from '../../mol-gl/webgl/render-target';
import { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { Texture } from '../../mol-gl/webgl/texture';
import { Camera } from '../camera';
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { HandleHelper } from '../helper/handle-helper';
import { Camera, ICamera } from '../camera';
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { ShaderCode } from '../../mol-gl/shader-code';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { ValueCell } from '../../mol-util';
import { Vec2 } from '../../mol-math/linear-algebra';
import { Helper } from '../helper/helper';
export const DrawPassParams = {
cameraHelper: PD.Group(CameraHelperParams)
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,
tDepthPrimitives: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
tDepthVolumes: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
uTexSize: UniformSpec('v2'),
dPackedDepth: DefineSpec('boolean'),
};
export const DefaultDrawPassProps = PD.getDefaultValues(DrawPassParams);
export type DrawPassProps = PD.Values<typeof DrawPassParams>
const DepthMergeShaderCode = ShaderCode('depth-merge', quad_vert, depthMerge_frag);
type DepthMergeRenderable = ComputeRenderable<Values<typeof DepthMergeSchema>>
function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Texture, depthTextureVolumes: Texture, packedDepth: boolean): DepthMergeRenderable {
const values: Values<typeof DepthMergeSchema> = {
...QuadValues,
tDepthPrimitives: ValueCell.create(depthTexturePrimitives),
tDepthVolumes: ValueCell.create(depthTextureVolumes),
uTexSize: ValueCell.create(Vec2.create(depthTexturePrimitives.getWidth(), depthTexturePrimitives.getHeight())),
dPackedDepth: ValueCell.create(packedDepth),
};
const schema = { ...DepthMergeSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', DepthMergeShaderCode, schema, values);
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 {
colorTarget: RenderTarget
depthTexture: Texture
packedDepth: boolean
private readonly drawTarget: RenderTarget
cameraHelper: CameraHelper
readonly colorTarget: RenderTarget
readonly depthTexture: Texture
readonly depthTexturePrimitives: Texture
private depthTarget: RenderTarget | null
readonly packedDepth: boolean
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private handleHelper: HandleHelper, props: Partial<DrawPassProps> = {}) {
const { gl, extensions, resources } = webgl;
const width = gl.drawingBufferWidth;
const height = gl.drawingBufferHeight;
this.colorTarget = webgl.createRenderTarget(width, height);
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?.supported;
}
constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) {
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.packedDepth = !extensions.depthTexture;
this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
if (!this.packedDepth) {
this.depthTexture.define(width, height);
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
}
const p = { ...DefaultDrawPassProps, ...props };
this.cameraHelper = new CameraHelper(webgl, p.cameraHelper);
this.depthTarget = webgl.createRenderTarget(width, height);
this.depthTexture = this.depthTarget.texture;
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', 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);
}
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) {
this.colorTarget.setSize(width, height);
if (this.depthTarget) {
const w = this.colorTarget.getWidth();
const h = this.colorTarget.getHeight();
if (width !== w || height !== h) {
this.colorTarget.setSize(width, height);
this.depthTarget.setSize(width, height);
} else {
this.depthTexture.define(width, height);
if (this.depthTargetPrimitives) {
this.depthTargetPrimitives.setSize(width, height);
} else {
this.depthTexturePrimitives.define(width, height);
}
if (this.depthTargetVolumes) {
this.depthTargetVolumes.setSize(width, height);
} else {
this.depthTextureVolumes.define(width, height);
}
ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
if (this.wboit?.supported) {
this.wboit.setSize(width, height);
}
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
}
}
setProps(props: Partial<DrawPassProps>) {
if (props.cameraHelper) this.cameraHelper.setProps(props.cameraHelper);
private _depthMerge() {
const { state, gl } = this.webgl;
this.depthMerge.update();
this.depthTarget.bind();
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.CULL_FACE);
state.depthMask(false);
state.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
this.depthMerge.render();
}
get props(): DrawPassProps {
return {
cameraHelper: { ...this.cameraHelper.props }
};
}
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
const { webgl, renderer, colorTarget, depthTarget } = this;
if (toDrawingBuffer) {
webgl.unbindFramebuffer();
this.colorTarget.bind();
renderer.clear(true);
// render opaque primitives
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
renderer.clearDepth();
renderer.renderWboitOpaque(scene.primitives, camera, null);
// render opaque volumes
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
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
this.postprocessing.target.bind();
} else {
colorTarget.bind();
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
}
this.wboit.render();
}
private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (toDrawingBuffer) {
this.drawTarget.bind();
} else {
this.colorTarget.bind();
if (!this.packedDepth) {
// TODO unlcear why it is not enough to call `attachFramebuffer` in `Texture.reset`
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
}
}
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight());
this.renderInternal('color', transparentBackground);
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 && depthTarget) {
depthTarget.bind();
this.renderInternal('depth', transparentBackground);
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.renderBlendedVolumeOpaque(scene.volumes, camera, this.depthTexturePrimitives);
// do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetVolumes) {
this.depthTargetVolumes.bind();
renderer.clear(false);
renderer.renderDepth(scene.volumes, camera, this.depthTexturePrimitives);
this.colorTarget.bind();
}
// 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);
}
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 (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
} else {
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.target.bind();
} else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) {
this.colorTarget.bind();
} else {
this.drawTarget.bind();
}
if (helper.debug.isEnabled) {
helper.debug.syncVisibility();
renderer.renderBlended(helper.debug.scene, camera, null);
}
if (helper.handle.isEnabled) {
renderer.renderBlended(helper.handle.scene, camera, null);
}
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
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 (PostprocessingPass.isEnabled(postprocessingProps)) {
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, 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, transparentBackground, postprocessingProps);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
}
}
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
const { renderer, scene, camera, debugHelper, cameraHelper, handleHelper } = this;
renderer.render(scene, camera, variant, true, transparentBackground);
if (debugHelper.isEnabled) {
debugHelper.syncVisibility();
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground);
}
if (handleHelper.isEnabled) {
renderer.render(handleHelper.scene, camera, variant, false, transparentBackground);
}
if (cameraHelper.isEnabled) {
cameraHelper.update(camera);
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground);
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

@@ -6,51 +6,59 @@
import { WebGLContext } from '../../mol-gl/webgl/context';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { Renderer } from '../../mol-gl/renderer';
import { Scene } from '../../mol-gl/scene';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DrawPass, DrawPassParams } from './draw';
import { PostprocessingPass, PostprocessingParams } from './postprocessing';
import { MultiSamplePass, MultiSampleParams } from './multi-sample';
import { DrawPass } from './draw';
import { PostprocessingParams } from './postprocessing';
import { MultiSamplePass, MultiSampleParams, MultiSampleHelper } from './multi-sample';
import { Camera } from '../camera';
import { Viewport } from '../camera/util';
import { HandleHelper } from '../helper/handle-helper';
import { PixelData } from '../../mol-util/image';
import { Helper } from '../helper/helper';
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
export const ImageParams = {
transparentBackground: PD.Boolean(false),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
drawPass: PD.Group(DrawPassParams),
cameraHelper: PD.Group(CameraHelperParams),
};
export type ImageProps = PD.Values<typeof ImageParams>
export class ImagePass {
private _width = 1024
private _height = 768
private _width = 0
private _height = 0
private _camera = new Camera()
private _transparentBackground = false
readonly props: ImageProps
private _colorTarget: RenderTarget
get colorTarget() { return this._colorTarget; }
readonly drawPass: DrawPass
private readonly postprocessing: PostprocessingPass
private readonly multiSample: MultiSamplePass
private readonly drawPass: DrawPass
private readonly multiSamplePass: MultiSamplePass
private readonly multiSampleHelper: MultiSampleHelper
private readonly helper: Helper
get width() { return this._width; }
get height() { return this._height; }
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, handleHelper: HandleHelper, props: Partial<ImageProps>) {
const p = { ...PD.getDefaultValues(ImageParams), ...props };
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
this._transparentBackground = p.transparentBackground;
this.drawPass = new DrawPass(webgl, 128, 128, enableWboit);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, handleHelper, p.drawPass);
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing);
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample);
this.helper = {
camera: new CameraHelper(webgl, this.props.cameraHelper),
debug: helper.debug,
handle: helper.handle,
};
this.setSize(this._width, this._height);
this.setSize(1024, 768);
}
setSize(width: number, height: number) {
@@ -60,24 +68,12 @@ export class ImagePass {
this._height = height;
this.drawPass.setSize(width, height);
this.postprocessing.setSize(width, height);
this.multiSample.setSize(width, height);
this.multiSamplePass.syncSize();
}
setProps(props: Partial<ImageProps> = {}) {
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground;
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing);
if (props.multiSample) this.multiSample.setProps(props.multiSample);
if (props.drawPass) this.drawPass.setProps(props.drawPass);
}
get props(): ImageProps {
return {
transparentBackground: this._transparentBackground,
postprocessing: this.postprocessing.props,
multiSample: this.multiSample.props,
drawPass: this.drawPass.props
};
Object.assign(this.props, props);
if (props.cameraHelper) this.helper.camera.setProps(props.cameraHelper);
}
render() {
@@ -85,26 +81,29 @@ export class ImagePass {
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height);
this._camera.update();
this.renderer.setViewport(0, 0, this._width, this._height);
if (this.multiSample.enabled) {
this.multiSample.render(false, this._transparentBackground);
this._colorTarget = this.multiSample.colorTarget;
if (MultiSamplePass.isEnabled(this.props.multiSample)) {
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(false, this._transparentBackground);
if (this.postprocessing.enabled) {
this.postprocessing.render(false);
this._colorTarget = this.postprocessing.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);
}
}
getImageData(width: number, height: number) {
getImageData(width: number, height: number, viewport?: Viewport) {
this.setSize(width, height);
this.render();
const pd = this.colorTarget.getPixelData();
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height);
this.colorTarget.bind();
const w = viewport?.width ?? width, h = viewport?.height ?? height;
const array = new Uint8Array(w * h * 4);
if (!viewport) {
this.webgl.readPixels(0, 0, w, h, array);
} else {
this.webgl.readPixels(viewport.x, height - viewport.y - viewport.height, w, h, array);
}
PixelData.flipY({ array, width: w, height: h });
return new ImageData(new Uint8ClampedArray(array), w, h);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -16,11 +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 } from './postprocessing';
import { PostprocessingProps } from './postprocessing';
import { DrawPass } from './draw';
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,
@@ -28,7 +32,7 @@ const ComposeSchema = {
uTexSize: UniformSpec('v2'),
uWeight: UniformSpec('f'),
};
const ComposeShaderCode = ShaderCode('compose', quad_vert, compose_frag);
type ComposeRenderable = ComputeRenderable<Values<typeof ComposeSchema>>
function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): ComposeRenderable {
@@ -40,8 +44,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
};
const schema = { ...ComposeSchema };
const shaderCode = ShaderCode('compose', quad_vert, compose_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
const renderItem = createComputeRenderItem(ctx, 'triangles', ComposeShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
@@ -52,69 +55,63 @@ export const MultiSampleParams = {
};
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
type Props = { multiSample: MultiSampleProps, postprocessing: PostprocessingProps }
export class MultiSamplePass {
props: MultiSampleProps
static isEnabled(props: MultiSampleProps) {
return props.mode !== 'off';
}
colorTarget: RenderTarget
private composeTarget: RenderTarget
private holdTarget: RenderTarget
private compose: ComposeRenderable
private sampleIndex = -1
private currentTime = 0
private lastRenderTime = 0
constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
const { gl } = webgl;
this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
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);
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);
this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props };
}
get enabled() {
if (this.props.mode === 'temporal') {
if (this.currentTime - this.lastRenderTime > 200) {
return this.sampleIndex !== -1;
} else {
this.sampleIndex = 0;
return false;
}
} else if (this.props.mode === 'on') {
return true;
} else {
return false;
syncSize() {
const width = this.drawPass.colorTarget.getWidth();
const height = this.drawPass.colorTarget.getHeight();
const [w, h] = this.compose.values.uTexSize.ref.value;
if (width !== w || height !== h) {
this.colorTarget.setSize(width, height);
this.composeTarget.setSize(width, height);
this.holdTarget.setSize(width, height);
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height));
}
}
update(changed: boolean, currentTime: number) {
if (changed) this.lastRenderTime = currentTime;
this.currentTime = currentTime;
}
setSize(width: number, height: number) {
this.colorTarget.setSize(width, height);
this.composeTarget.setSize(width, height);
this.holdTarget.setSize(width, height);
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height));
}
setProps(props: Partial<MultiSampleProps>) {
if (props.mode !== undefined) this.props.mode = props.mode;
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel;
}
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
if (this.props.mode === 'temporal') {
this.renderTemporalMultiSample(toDrawingBuffer, transparentBackground);
render(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
if (props.multiSample.mode === 'temporal') {
return this.renderTemporalMultiSample(sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
} else {
this.renderMultiSample(toDrawingBuffer, transparentBackground);
this.renderMultiSample(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
return sampleIndex;
}
}
private renderMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) {
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this;
private bindOutputTarget(toDrawingBuffer: boolean) {
if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
} else {
this.colorTarget.bind();
}
}
private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const { compose, composeTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
// based on the Multisample Anti-Aliasing Render Pass
@@ -122,18 +119,16 @@ export class MultiSamplePass {
//
// This manual approach to MSAA re-renders the scene once for
// each sample with camera jitter and accumulates the results.
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ];
const offsetList = JitterVectors[ Math.max(0, Math.min(props.multiSample.sampleLevel, 5)) ];
const { x, y, width, height } = camera.viewport;
const baseSampleWeight = 1.0 / offsetList.length;
const roundingRange = 1 / 32;
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
const width = drawPass.colorTarget.getWidth();
const height = drawPass.colorTarget.getHeight();
// render the scene multiple times, each slightly jitter offset
// from the last and accumulate the results.
for (let i = 0; i < offsetList.length; ++i) {
@@ -148,19 +143,18 @@ export class MultiSamplePass {
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution;
ValueCell.update(compose.values.uWeight, sampleWeight);
// render scene and optionally postprocess
drawPass.render(false, transparentBackground);
if (postprocessing.enabled) postprocessing.render(false);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
// compose rendered scene with compose target
composeTarget.bind();
gl.viewport(0, 0, width, height);
state.enable(gl.BLEND);
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.depthMask(false);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
if (i === 0) {
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -172,12 +166,10 @@ export class MultiSamplePass {
ValueCell.update(compose.values.tColor, composeTarget.texture);
compose.update();
if (toDrawingBuffer) {
webgl.unbindFramebuffer();
} else {
this.colorTarget.bind();
}
gl.viewport(0, 0, width, height);
this.bindOutputTarget(toDrawingBuffer);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.disable(gl.BLEND);
compose.render();
@@ -185,8 +177,8 @@ export class MultiSamplePass {
camera.update();
}
private renderTemporalMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) {
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this;
private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
// based on the Multisample Anti-Aliasing Render Pass
@@ -194,79 +186,73 @@ export class MultiSamplePass {
//
// This manual approach to MSAA re-renders the scene once for
// each sample with camera jitter and accumulates the results.
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ];
const offsetList = JitterVectors[ Math.max(0, Math.min(props.multiSample.sampleLevel, 5)) ];
if (this.sampleIndex === -1) return;
if (this.sampleIndex >= offsetList.length) {
this.sampleIndex = -1;
return;
}
if (sampleIndex === -2 || sampleIndex >= offsetList.length) return -2;
const i = this.sampleIndex;
const { x, y, width, height } = camera.viewport;
const sampleWeight = 1.0 / offsetList.length;
if (i === 0) {
drawPass.render(false, transparentBackground);
if (postprocessing.enabled) postprocessing.render(false);
if (sampleIndex === -1) {
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
ValueCell.update(compose.values.uWeight, 1.0);
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
holdTarget.bind();
state.disable(gl.BLEND);
compose.render();
}
const sampleWeight = 1.0 / offsetList.length;
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.uWeight, sampleWeight);
compose.update();
const width = drawPass.colorTarget.getWidth();
const height = drawPass.colorTarget.getHeight();
// render the scene multiple times, each slightly jitter offset
// from the last and accumulate the results.
const numSamplesPerFrame = Math.pow(2, this.props.sampleLevel);
for (let i = 0; i < numSamplesPerFrame; ++i) {
const offset = offsetList[this.sampleIndex];
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
camera.update();
// render scene and optionally postprocess
drawPass.render(false, transparentBackground);
if (postprocessing.enabled) postprocessing.render(false);
// compose rendered scene with compose target
composeTarget.bind();
state.enable(gl.BLEND);
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.depthMask(false);
if (this.sampleIndex === 0) {
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
compose.render();
sampleIndex += 1;
} else {
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
ValueCell.update(compose.values.uWeight, sampleWeight);
compose.update();
this.sampleIndex += 1;
if (this.sampleIndex >= offsetList.length ) break;
// render the scene multiple times, each slightly jitter offset
// from the last and accumulate the results.
const numSamplesPerFrame = Math.pow(2, Math.max(0, props.multiSample.sampleLevel - 2));
for (let i = 0; i < numSamplesPerFrame; ++i) {
const offset = offsetList[sampleIndex];
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
camera.update();
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
// compose rendered scene with compose target
composeTarget.bind();
state.enable(gl.BLEND);
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
if (sampleIndex === 0) {
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
compose.render();
sampleIndex += 1;
if (sampleIndex >= offsetList.length ) break;
}
}
const accumulationWeight = this.sampleIndex * sampleWeight;
this.bindOutputTarget(toDrawingBuffer);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
const accumulationWeight = sampleIndex * sampleWeight;
if (accumulationWeight > 0) {
ValueCell.update(compose.values.uWeight, 1.0);
ValueCell.update(compose.values.tColor, composeTarget.texture);
compose.update();
if (toDrawingBuffer) {
webgl.unbindFramebuffer();
} else {
this.colorTarget.bind();
}
gl.viewport(0, 0, width, height);
state.disable(gl.BLEND);
compose.render();
}
@@ -274,12 +260,6 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight);
ValueCell.update(compose.values.tColor, holdTarget.texture);
compose.update();
if (toDrawingBuffer) {
webgl.unbindFramebuffer();
} else {
this.colorTarget.bind();
}
gl.viewport(0, 0, width, height);
if (accumulationWeight === 0) state.disable(gl.BLEND);
else state.enable(gl.BLEND);
compose.render();
@@ -287,7 +267,8 @@ export class MultiSamplePass {
camera.viewOffset.enabled = false;
camera.update();
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1;
return sampleIndex >= offsetList.length ? -2 : sampleIndex;
}
}
@@ -329,4 +310,21 @@ JitterVectors.forEach(offsetList => {
offset[0] *= 0.0625;
offset[1] *= 0.0625;
});
});
});
export class MultiSampleHelper {
private sampleIndex = -2
update(changed: boolean, props: MultiSampleProps) {
if (changed) this.sampleIndex = -1;
return props.mode === 'temporal' ? this.sampleIndex !== -2 : false;
}
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
}
constructor(private multiSamplePass: MultiSamplePass) {
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { DrawPass } from './draw';
import { PickPass } from './pick';
import { MultiSamplePass } from './multi-sample';
import { WebGLContext } from '../../mol-gl/webgl/context';
export class Passes {
readonly draw: DrawPass
readonly pick: PickPass
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.multiSample = new MultiSamplePass(webgl, this.draw);
}
updateSize() {
const { gl } = this.webgl;
this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
this.pick.syncSize();
this.multiSample.syncSize();
}
}

View File

@@ -1,131 +1,251 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { 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 { PickingId } from '../../mol-geo/geometry/picking';
import { decodeFloatRGB } from '../../mol-util/float-packing';
import { Camera } from '../camera';
import { HandleHelper } from '../helper/handle-helper';
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';
import { Vec3 } from '../../mol-math/linear-algebra';
import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
import { Camera, ICamera } from '../camera';
import { StereoCamera } from '../camera/stereo';
import { cameraUnproject } from '../camera/util';
import { Viewport } from '../camera/util';
import { Helper } from '../helper/helper';
import { DrawPass } from './draw';
const NullId = Math.pow(2, 24) - 2;
export type PickData = { id: PickingId, position: Vec3 }
export class PickPass {
pickDirty = true
readonly objectPickTarget: RenderTarget
readonly instancePickTarget: RenderTarget
readonly groupPickTarget: RenderTarget
readonly depthPickTarget: RenderTarget
objectPickTarget: RenderTarget
instancePickTarget: RenderTarget
groupPickTarget: RenderTarget
private objectBuffer: Uint8Array
private instanceBuffer: Uint8Array
private groupBuffer: Uint8Array
private pickScale: number
private pickWidth: number
private pickHeight: number
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private handleHelper: HandleHelper, private pickBaseScale: number) {
const { gl } = webgl;
const width = gl.drawingBufferWidth;
const height = gl.drawingBufferHeight;
this.pickScale = pickBaseScale / webgl.pixelRatio;
this.pickWidth = Math.round(width * this.pickScale);
this.pickHeight = Math.round(height * this.pickScale);
constructor(private webgl: WebGLContext, private drawPass: DrawPass, readonly pickBaseScale: number) {
const pickScale = pickBaseScale / webgl.pixelRatio;
this.pickWidth = Math.ceil(drawPass.colorTarget.getWidth() * pickScale);
this.pickHeight = Math.ceil(drawPass.colorTarget.getHeight() * pickScale);
this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
this.setupBuffers();
this.depthPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
}
get drawingBufferHeight() {
return this.drawPass.colorTarget.getHeight();
}
syncSize() {
const pickScale = this.pickBaseScale / this.webgl.pixelRatio;
const pickWidth = Math.ceil(this.drawPass.colorTarget.getWidth() * pickScale);
const pickHeight = Math.ceil(this.drawPass.colorTarget.getHeight() * pickScale);
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
this.pickWidth = pickWidth;
this.pickHeight = pickHeight;
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
this.depthPickTarget.setSize(this.pickWidth, this.pickHeight);
}
}
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
const depth = this.drawPass.depthTexturePrimitives;
renderer.clear(false);
renderer.renderPick(scene.primitives, camera, variant, null);
renderer.renderPick(scene.volumes, camera, variant, depth);
renderer.renderPick(helper.handle.scene, 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');
this.instancePickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'pickInstance');
this.groupPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'pickGroup');
this.depthPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'depth');
}
}
export class PickHelper {
dirty = true
private objectBuffer: Uint8Array
private instanceBuffer: Uint8Array
private groupBuffer: Uint8Array
private depthBuffer: Uint8Array
private viewport = Viewport()
private pickScale: number
private pickX: number
private pickY: number
private pickWidth: number
private pickHeight: number
private halfPickWidth: number
private setupBuffers() {
const bufferSize = this.pickWidth * this.pickHeight * 4;
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
this.objectBuffer = new Uint8Array(bufferSize);
this.instanceBuffer = new Uint8Array(bufferSize);
this.groupBuffer = new Uint8Array(bufferSize);
this.depthBuffer = new Uint8Array(bufferSize);
}
}
setSize(width: number, height: number) {
this.pickScale = this.pickBaseScale / this.webgl.pixelRatio;
this.pickWidth = Math.round(width * this.pickScale);
this.pickHeight = Math.round(height * this.pickScale);
setViewport(x: number, y: number, width: number, height: number) {
Viewport.set(this.viewport, x, y, width, height);
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
this.pickScale = this.pickPass.pickBaseScale / this.webgl.pixelRatio;
this.pickX = Math.ceil(x * this.pickScale);
this.pickY = Math.ceil(y * this.pickScale);
this.setupBuffers();
}
const pickWidth = Math.ceil(width * this.pickScale);
const pickHeight = Math.ceil(height * this.pickScale);
render() {
const { renderer, scene, camera, handleHelper: { scene: handleScene } } = this;
renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
this.pickWidth = pickWidth;
this.pickHeight = pickHeight;
this.halfPickWidth = Math.floor(this.pickWidth / 2);
this.objectPickTarget.bind();
renderer.render(scene, camera, 'pickObject', true, false);
renderer.render(handleScene, camera, 'pickObject', false, false);
this.instancePickTarget.bind();
renderer.render(scene, camera, 'pickInstance', true, false);
renderer.render(handleScene, camera, 'pickInstance', false, false);
this.groupPickTarget.bind();
renderer.render(scene, camera, 'pickGroup', true, false);
renderer.render(handleScene, camera, 'pickGroup', false, false);
this.pickDirty = false;
this.setupBuffers();
}
}
private syncBuffers() {
const { webgl } = this;
const { pickX, pickY, pickWidth, pickHeight } = this;
this.objectPickTarget.bind();
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.objectBuffer);
this.pickPass.objectPickTarget.bind();
this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.objectBuffer);
this.instancePickTarget.bind();
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.instanceBuffer);
this.pickPass.instancePickTarget.bind();
this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.instanceBuffer);
this.groupPickTarget.bind();
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.groupBuffer);
this.pickPass.groupPickTarget.bind();
this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.groupBuffer);
this.pickPass.depthPickTarget.bind();
this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.depthBuffer);
}
private getBufferIdx(x: number, y: number): number {
return (y * this.pickWidth + x) * 4;
}
private getDepth(x: number, y: number): number {
const idx = this.getBufferIdx(x, y);
const b = this.depthBuffer;
return unpackRGBAToDepth(b[idx], b[idx + 1], b[idx + 2], b[idx + 3]);
}
private getId(x: number, y: number, buffer: Uint8Array) {
const idx = (y * this.pickWidth + x) * 4;
const idx = this.getBufferIdx(x, y);
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
}
identify(x: number, y: number): PickingId | undefined {
private render(camera: Camera | StereoCamera) {
const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this;
const { renderer, scene, helper } = this;
renderer.setTransparentBackground(false);
renderer.setDrawingBufferSize(this.pickPass.objectPickTarget.getWidth(), this.pickPass.objectPickTarget.getHeight());
if (StereoCamera.is(camera)) {
renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight);
this.pickPass.render(renderer, camera.left, scene, helper);
renderer.setViewport(pickX + halfPickWidth, pickY, pickWidth - halfPickWidth, pickHeight);
this.pickPass.render(renderer, camera.right, scene, helper);
} else {
renderer.setViewport(pickX, pickY, pickWidth, pickHeight);
this.pickPass.render(renderer, camera, scene, helper);
}
this.dirty = false;
}
identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
const { webgl, pickScale } = this;
if (webgl.isContextLost) return;
const { gl } = webgl;
if (this.pickDirty) {
this.render();
x *= webgl.pixelRatio;
y *= webgl.pixelRatio;
y = this.pickPass.drawingBufferHeight - y; // flip y
const { viewport } = this;
// check if within viewport
if (x < viewport.x ||
y < viewport.y ||
x > viewport.x + viewport.width ||
y > viewport.y + viewport.height
) return;
if (this.dirty) {
this.render(camera);
this.syncBuffers();
}
x *= webgl.pixelRatio;
y *= webgl.pixelRatio;
y = gl.drawingBufferHeight - y; // flip y
const xv = x - viewport.x;
const yv = y - viewport.y;
const xp = Math.round(x * pickScale);
const yp = Math.round(y * pickScale);
const xp = Math.floor(xv * pickScale);
const yp = Math.floor(yv * pickScale);
const objectId = this.getId(xp, yp, this.objectBuffer);
if (objectId === -1) return;
// console.log('objectId', objectId);
if (objectId === -1 || objectId === NullId) return;
const instanceId = this.getId(xp, yp, this.instanceBuffer);
if (instanceId === -1) return;
// console.log('instanceId', instanceId);
if (instanceId === -1 || instanceId === NullId) return;
const groupId = this.getId(xp, yp, this.groupBuffer);
if (groupId === -1) return;
// console.log('groupId', groupId);
if (groupId === -1 || groupId === NullId) return;
return { objectId, instanceId, groupId };
const z = this.getDepth(xp, yp);
const position = Vec3.create(x, viewport.height - y, z);
if (StereoCamera.is(camera)) {
const halfWidth = Math.floor(viewport.width / 2);
if (x > viewport.x + halfWidth) {
position[0] = viewport.x + (xv - halfWidth) * 2;
cameraUnproject(position, position, viewport, camera.right.inverseProjectionView);
} else {
position[0] = viewport.x + xv * 2;
cameraUnproject(position, position, viewport, camera.left.inverseProjectionView);
}
} else {
cameraUnproject(position, position, viewport, camera.inverseProjectionView);
}
// console.log({ { objectId, instanceId, groupId }, position} );
return { id: { objectId, instanceId, groupId }, position };
}
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport) {
this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
}
}

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2019-2020 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,174 @@ 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 } 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 } from '../../mol-canvas3d/camera';
import { produce } from 'immer';
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';
import quad_vert from '../../mol-gl/shader/quad.vert';
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag';
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'),
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()),
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'),
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),
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'),
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
dOrthographic: DefineSpec('number'),
@@ -34,46 +189,25 @@ 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'),
dPackedDepth: DefineSpec('boolean'),
};
export const PostprocessingParams = {
occlusion: PD.MappedStatic('off', {
on: PD.Group({
kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
outline: PD.MappedStatic('off', {
on: PD.Group({
scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
threshold: PD.Numeric(0.8, { min: 0, max: 5, step: 0.01 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Draw outline around 3D objects' })
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, packedDepth: boolean, props: Partial<PostprocessingProps>): PostprocessingRenderable {
const p = { ...PD.getDefaultValues(PostprocessingParams), ...props };
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),
tDepth: ValueCell.create(depthTexture),
tOutlines: ValueCell.create(outlinesTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
dOrthographic: ValueCell.create(0),
@@ -82,17 +216,15 @@ 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),
dOcclusionEnable: ValueCell.create(p.occlusion.name === 'on'),
dOcclusionKernelSize: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.kernelSize : 4),
uOcclusionBias: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.bias : 0.5),
uOcclusionRadius: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.radius : 64),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
dOutlineEnable: ValueCell.create(p.outline.name === 'on'),
uOutlineScale: ValueCell.create((p.outline.name === 'on' ? p.outline.params.scale : 1) * ctx.pixelRatio),
uOutlineThreshold: ValueCell.create(p.outline.name === 'on' ? p.outline.params.threshold : 0.8),
dOcclusionEnable: ValueCell.create(false),
dPackedDepth: ValueCell.create(packedDepth),
dOutlineEnable: ValueCell.create(false),
dOutlineScale: ValueCell.create(1),
uOutlineThreshold: ValueCell.create(0.33),
};
const schema = { ...PostprocessingSchema };
@@ -102,73 +234,334 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
return createComputeRenderable(renderItem, values);
}
export class PostprocessingPass {
target: RenderTarget
props: PostprocessingProps
renderable: PostprocessingRenderable
export const PostprocessingParams = {
occlusion: PD.MappedStatic('on', {
on: PD.Group({
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: 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('smaa', {
fxaa: PD.Group(FxaaParams),
smaa: PD.Group(SmaaParams),
off: PD.Group({})
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
constructor(private webgl: WebGLContext, private camera: Camera, drawPass: DrawPass, props: Partial<PostprocessingProps>) {
const { gl } = webgl;
this.target = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props };
const { colorTarget, depthTexture, packedDepth } = drawPass;
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.props);
export class PostprocessingPass {
static isEnabled(props: PostprocessingProps) {
return props.occlusion.name === 'on' || props.outline.name === 'on';
}
get enabled() {
return this.props.occlusion.name === 'on' || this.props.outline.name === 'on';
readonly target: RenderTarget
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 scale: number
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
this.scale = 1 / this.webgl.pixelRatio;
const { colorTarget, depthTexture } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
this.nSamples = 1;
this.blurKernelSize = 1;
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.scale);
const sh = Math.floor(height * this.scale);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
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);
}
setSize(width: number, height: number) {
this.target.setSize(width, height);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
this.target.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.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
}
}
setProps(props: Partial<PostprocessingProps>) {
this.props = produce(this.props, p => {
if (props.occlusion !== undefined) {
p.occlusion.name = props.occlusion.name;
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, props.occlusion.name === 'on');
if (props.occlusion.name === 'on') {
p.occlusion.params = { ...props.occlusion.params };
ValueCell.updateIfChanged(this.renderable.values.dOcclusionKernelSize, props.occlusion.params.kernelSize);
ValueCell.updateIfChanged(this.renderable.values.uOcclusionBias, props.occlusion.params.bias);
ValueCell.updateIfChanged(this.renderable.values.uOcclusionRadius, props.occlusion.params.radius);
}
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.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection);
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.updateIfChanged(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;
let kernel = getBlurKernel(this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
if (props.outline !== undefined) {
p.outline.name = props.outline.name;
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, props.outline.name === 'on');
if (props.outline.name === 'on') {
p.outline.params = { ...props.outline.params };
ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale);
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
}
}
});
}
this.renderable.update();
}
if (props.outline.name === 'on') {
const factor = Math.pow(1000, props.outline.params.threshold) / 1000;
const maxPossibleViewZDiff = factor * (camera.far - camera.near);
const outlineScale = props.outline.params.scale - 1;
render(toDrawingBuffer: boolean) {
ValueCell.update(this.renderable.values.uFar, this.camera.far);
ValueCell.update(this.renderable.values.uNear, this.camera.near);
ValueCell.update(this.renderable.values.uFogFar, this.camera.fogFar);
ValueCell.update(this.renderable.values.uFogNear, this.camera.fogNear);
ValueCell.update(this.renderable.values.dOrthographic, this.camera.state.mode === 'orthographic' ? 1 : 0);
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);
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
}
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;
if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
} else {
this.target.bind();
}
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
const { x, y, width, height } = camera.viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
}
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
this.updateState(camera, transparentBackground, backgroundColor, props);
if (props.outline.name === 'on') {
this.outlinesTarget.bind();
this.outlinesRenderable.render();
}
if (props.occlusion.name === 'on') {
const { x, y, width, height } = camera.viewport;
const sx = Math.floor(x * this.scale);
const sy = Math.floor(y * this.scale);
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
this.webgl.gl.viewport(sx, sy, sw, sh);
this.webgl.gl.scissor(sx, sy, sw, sh);
this.ssaoFramebuffer.bind();
this.ssaoRenderable.render();
this.ssaoBlurFirstPassFramebuffer.bind();
this.ssaoBlurFirstPassRenderable.render();
this.ssaoBlurSecondPassFramebuffer.bind();
this.ssaoBlurSecondPassRenderable.render();
this.webgl.gl.viewport(x, y, width, height);
this.webgl.gl.scissor(x, y, width, height);
}
if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
} else {
this.target.bind();
}
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';
}
readonly target: RenderTarget
private readonly fxaa: FxaaPass
private readonly smaa: SmaaPass
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);
} else if (props.antialiasing.name === 'smaa') {
if (!this.smaa.supported) {
throw new Error('SMAA not supported, missing "HTMLImageElement"');
}
this._renderSmaa(camera, toDrawingBuffer, props);
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,143 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
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';
const EvaluateWboitSchema = {
...QuadSchema,
tWboitA: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tWboitB: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const EvaluateWboitShaderCode = ShaderCode('evaluate-wboit', quad_vert, evaluateWboit_frag);
type EvaluateWboitRenderable = ComputeRenderable<Values<typeof EvaluateWboitSchema>>
function getEvaluateWboitRenderable(ctx: WebGLContext, wboitATexture: Texture, wboitBTexture: Texture): EvaluateWboitRenderable {
const values: Values<typeof EvaluateWboitSchema> = {
...QuadValues,
tWboitA: ValueCell.create(wboitATexture),
tWboitB: ValueCell.create(wboitBTexture),
uTexSize: ValueCell.create(Vec2.create(wboitATexture.getWidth(), wboitATexture.getHeight())),
};
const schema = { ...EvaluateWboitSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateWboitShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
//
export class WboitPass {
private readonly renderable: EvaluateWboitRenderable
private readonly framebuffer: Framebuffer
private readonly textureA: Texture
private readonly textureB: Texture
private _supported = false;
get supported() {
return this._supported;
}
bind() {
const { state, gl } = this.webgl;
this.framebuffer.bind();
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
state.disable(gl.DEPTH_TEST);
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
}
render() {
const { state, gl } = this.webgl;
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
this.renderable.update();
this.renderable.render();
}
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
this.textureA.define(width, height);
this.textureB.define(width, height);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
}
}
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) {
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);
this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest');
this.textureB.define(width, height);
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
this.framebuffer = resources.framebuffer();
this._supported = true;
this._init();
}
}

View File

@@ -5,14 +5,14 @@
*/
/** Set canvas size taking `devicePixelRatio` into account */
export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height: number) {
canvas.width = Math.round(window.devicePixelRatio * width);
canvas.height = Math.round(window.devicePixelRatio * height);
export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height: number, scale = 1) {
canvas.width = Math.round(window.devicePixelRatio * scale * width);
canvas.height = Math.round(window.devicePixelRatio * scale * height);
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` });
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
let width = window.innerWidth;
let height = window.innerHeight;
if (container !== document.body) {
@@ -20,7 +20,7 @@ export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
width = bounds.right - bounds.left;
height = bounds.bottom - bounds.top;
}
setCanvasSize(canvas, width, height);
setCanvasSize(canvas, width, height, scale);
}
function _canvasToBlob(canvas: HTMLCanvasElement, callback: BlobCallback, type?: string, quality?: any) {

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';

View File

@@ -4,13 +4,13 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Interval from './int/interval';
import OrderedSet from './int/ordered-set';
import Segmentation from './int/segmentation';
import SortedArray from './int/sorted-array';
import Tuple from './int/tuple';
import LinkedIndex from './int/linked-index';
import IntMap from './int/map';
import Iterator from './iterator';
import { Interval } from './int/interval';
import { OrderedSet } from './int/ordered-set';
import { Segmentation } from './int/segmentation';
import { SortedArray } from './int/sorted-array';
import { IntTuple as Tuple } from './int/tuple';
import { LinkedIndex } from './int/linked-index';
import { IntMap } from './int/map';
import { Iterator } from './iterator';
export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, IntMap, Iterator };

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Interval from '../interval';
import { Interval } from '../interval';
describe('interval', () => {
function testI(name: string, a: Interval, b: Interval) {

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import LinkedIndex from '../linked-index';
import { LinkedIndex } from '../linked-index';
describe('linked-index', () => {
it('initial state', () => {

View File

@@ -4,9 +4,9 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import OrderedSet from '../ordered-set';
import Interval from '../interval';
import SortedArray from '../sorted-array';
import { OrderedSet } from '../ordered-set';
import { Interval } from '../interval';
import { SortedArray } from '../sorted-array';
describe('ordered set', () => {
function ordSetToArray(set: OrderedSet) {

View File

@@ -4,9 +4,9 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import OrderedSet from '../ordered-set';
import Interval from '../interval';
import Segmentation from '../segmentation';
import { OrderedSet } from '../ordered-set';
import { Interval } from '../interval';
import { Segmentation } from '../segmentation';
describe('segments', () => {
const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]);

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