Compare commits

..

223 Commits

Author SHA1 Message Date
Alexander Rose
e602705e6d 3.31.0 2023-02-05 16:19:17 -08:00
Alexander Rose
b7d126b39b package updates 2023-02-05 16:13:46 -08:00
Alexander Rose
80b2864da8 remove unused travis file 2023-02-05 16:13:36 -08:00
Alexander Rose
36ba6f035a increase max intensity in light/ambient params 2023-02-05 15:45:39 -08:00
Alexander Rose
b84bbdace2 refactor PCA focus some more 2023-02-05 15:08:17 -08:00
Alexander Rose
750b2f69fc Merge pull request #710 from molstar/console-stats-timing
add consoleStats, improve timer
2023-02-05 14:00:51 -08:00
Alexander Rose
6ffe051a8e Merge branch 'master' of https://github.com/molstar/molstar 2023-02-05 13:43:36 -08:00
Alexander Rose
bc3640b264 add asymIds to OperatorGroup 2023-02-05 13:43:27 -08:00
Alexander Rose
8746b6e2f4 Observable canvas commit 2023-02-05 13:41:42 -08:00
dsehnal
445977d99b Fix data-source node workaround 2023-02-05 22:34:38 +01:00
Alexander Rose
0f0185e18c add fast boundary helper and unit trait 2023-02-05 13:34:33 -08:00
dsehnal
a748b1581e refactor PCA focus 2023-02-05 22:24:40 +01:00
Ke Ma
af1e06203b Dev focus pca (#624)
* viewer camera change based on Pca

* minor code refactor

* update author

* Update src/mol-plugin-state/manager/focus-camera/focus-first-residue.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* Update src/mol-plugin-state/manager/focus-camera/focus-first-residue.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* Update src/mol-plugin-state/manager/focus-camera/focus-first-residue.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* Update src/mol-plugin-state/manager/focus-camera/focus-first-residue.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* Update src/mol-plugin-state/manager/focus-camera/focus-first-residue.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* Update src/mol-plugin-ui/structure/components.tsx

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* Update src/mol-plugin-state/manager/focus-camera/focus-first-residue.ts

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>

* revise

* deepclone

* chunked-array

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-02-05 20:27:25 +01:00
Alexander Rose
356cf008ce add consoleStats, improve timer 2023-02-04 19:25:08 -08:00
Alexander Rose
07284e7e3d handle null-texture in calcTextureMeshColorSmoothing 2023-02-04 13:58:02 -08:00
Alexander Rose
72055442b7 add Color.contrast/.luminance 2023-02-04 10:30:14 -08:00
Alexander Rose
5fa7d84c23 axes camera-helper tweaks 2023-02-03 07:59:26 -08:00
midlik
de46f08bf4 Minor changes regarding HeadlessPluginContext (#708)
* Minor changes regarding HeadlessPluginContext

* Tweaks
2023-02-03 12:31:39 +01:00
Alexander Rose
fde395e2fa Merge pull request #705 from molstar/camera-helper-params
add axes camera-helper customization options
2023-02-01 08:10:47 -08:00
midlik
da1e55250e Added HeadlessPluginContext, Canvas3DRenderer, image-renderer example (#700)
* Added HeadlessPluginContext, Canvas3DRenderer, image-renderer example

* Fixed dependencies in package.json

* Removed gl from deps

* Removed gl, pngjs, jpeg-js from dependencies, importing via LazyImports

* Updated docs in src/examples/image-renderer/index.ts

* Added gl, pngjs, jpeg-js to optionalDependencies

* Canvas3DRenderer: default cameraResetDurationMs: 0 (necessary when changing camera between renderings)

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2023-02-01 16:56:38 +01:00
Alexander Rose
a4ab117d14 support WEBGL_provoking_vertex 2023-01-31 19:56:03 -08:00
Alexander Rose
4cd7088eb8 add axes camera-helper customization options 2023-01-31 19:41:14 -08:00
Alexander Rose
8e1876fc25 update camera/handle helper on pixelRatio changes 2023-01-31 19:35:09 -08:00
Alexander Rose
29693ebe8c fix text zOffset with orthographic projection 2023-01-31 19:09:36 -08:00
Alexander Rose
a8ee7bfcbe fix uModelView construction 2023-01-31 19:04:46 -08:00
Alexander Rose
2037dad03d 3.30.0 2023-01-29 16:28:11 -08:00
Alexander Rose
0a9fb603f6 changelog 2023-01-29 16:22:25 -08:00
Alexander Rose
0b7dadd345 package updates 2023-01-29 16:21:56 -08:00
Alexander Rose
a3645f5acc use single channel for downsampled depth when possible 2023-01-29 16:08:58 -08:00
Alexander Rose
92f409d6fc fix downsampled ssao 2023-01-29 16:08:18 -08:00
Alexander Rose
c28dd8135c perf tweaks
- reuse vec3 in getMatrices
- fast path in createElementSphereImpostor
2023-01-28 23:08:39 -08:00
Alexander Rose
3baa03ccdc adjust outlines based on view distance 2023-01-22 14:21:37 -08:00
Alexander Rose
659e96d93c Merge pull request #699 from MadCatX/ntc_tube_discontinuity
Fix NtC tube and Confal pyramids mesh when there are discontinuous chains
2023-01-22 14:14:51 -08:00
Alexander Rose
365d7d46fd fix SSAO artefacts with high bias values 2023-01-22 11:47:17 -08:00
Alexander Rose
f8284508e1 Merge pull request #695 from molstar/state-images
state snapshot images
2023-01-22 11:45:16 -08:00
Alexander Rose
e9c36c2375 fix boundary-helper regression 2023-01-19 21:43:57 -08:00
Michal Malý
5d269fd77c Check for discontinuities when buiilding Confal pyramids mesh 2023-01-17 17:30:30 +01:00
Michal Malý
0064293e01 Unregister ConfalPyramids 2023-01-17 17:30:30 +01:00
Michal Malý
ba4b6f70d3 Draw gaps in NtC tube if there are discontinuities in a chain 2023-01-17 17:30:28 +01:00
Alexander Rose
9d056a85ec state snapshot images 2023-01-15 22:59:33 -08:00
Alexander Rose
3ed17fce6b update gh workflow gl version 2023-01-15 22:53:34 -08:00
Alexander Rose
d656dd0d18 improve boundary calculation performance 2023-01-15 22:49:07 -08:00
Alexander Rose
27c92085c7 changelog 2023-01-15 14:06:53 -08:00
Alexander Rose
94e9462af8 Merge pull request #526 from MadCatX/ntc_tube_2
Add NtC Tube representation
2023-01-15 14:05:29 -08:00
Michal Malý
2e52ccf5d8 Add NtC Tube representation 2023-01-15 22:18:13 +01:00
Michal Malý
b15196a284 Reset ConfalPyramidsIterator internal state correctly when switching
chains
2023-01-15 22:08:45 +01:00
Michal Malý
d29cc85439 Factor out common Dnatco code 2023-01-15 22:08:41 +01:00
Alexander Rose
30367cf239 lint spacing 2023-01-15 11:10:16 -08:00
Alexander Rose
dcf9d1c3bb 3.29.0 2023-01-15 10:06:42 -08:00
Alexander Rose
c0c2e4ce4a changelog 2023-01-15 10:01:35 -08:00
Alexander Rose
3557f4e738 schema updates 2023-01-15 10:00:38 -08:00
Alexander Rose
6595c00cff package updates 2023-01-15 09:58:20 -08:00
Alexander Rose
c612018ba8 Merge pull request #693 from molstar/dim-unmarked
support dim unmarked groups & marker edge strength
2023-01-15 09:04:35 -08:00
Alexander Rose
ec06ef2dfb fix missing uniforms 2023-01-14 13:14:16 -08:00
Alexander Rose
2febf45700 support dim unmarked groups & marker edge strength 2023-01-14 12:46:26 -08:00
Alexander Rose
3e6066a1a1 add .getCenter and .center to Camera 2023-01-14 12:41:41 -08:00
Alexander Rose
b61e5c76db add default to ParamDefinition.Values type 2023-01-14 12:40:13 -08:00
Alexander Rose
bdee4859f2 generic loci support for overpaint, substance, clipping 2023-01-14 12:39:25 -08:00
Alexander Rose
b5e45aae95 Merge pull request #692 from molstar/misc2
Misc2
2023-01-09 20:18:01 -08:00
Alexander Rose
7fd12c5622 tweaks/fixes 2023-01-09 20:16:33 -08:00
Alexander Rose
47442be989 changelog 2023-01-08 21:30:41 -08:00
Alexander Rose
3a484dc41a make SymmetryOperator.createMapping monomorphic 2023-01-08 21:25:23 -08:00
Alexander Rose
9a2dfd7e57 improve calculateInvariantBoundingSphere 2023-01-08 21:25:07 -08:00
Alexander Rose
c9a168a138 improve checks in UnitsRepresentation setVisualState 2023-01-08 21:24:35 -08:00
Alexander Rose
3726f28eeb defer calculation of more scene properties 2023-01-08 21:24:21 -08:00
Alexander Rose
ead25d6a9c tweak BoundaryHelper performance 2023-01-08 21:24:14 -08:00
Alexander Rose
55f4abb6be add repr/theme registry .clear method 2023-01-08 21:24:04 -08:00
dsehnal
2e013fafc8 Add StructureElement.Loci.forEachLocation 2023-01-05 13:07:21 +01:00
dsehnal
0809379f91 update build badge 2023-01-02 14:23:36 +01:00
dsehnal
0d4a95f5af workflow name 2023-01-02 14:21:30 +01:00
David Sehnal
2e9129a71c Fix some cyclic imports & reduce const enum usage (#680)
* fix mol-theme/color

* fix mol-theme/size

* remove unnecessary const enums

* remove Column.ValueType const enum

* NumberTypes

* ValueKindConst => ValueKinds

* more const enum fixes

* more const enums

* readme

* update filename

* typing

* FeatureTypes
2022-12-31 11:22:18 +01:00
David Sehnal
7384bebf4e Fix some cyclic imports & reduce const enum usage (#680)
* fix mol-theme/color

* fix mol-theme/size

* remove unnecessary const enums

* remove Column.ValueType const enum

* NumberTypes

* ValueKindConst => ValueKinds

* more const enum fixes

* more const enums

* readme

* update filename

* typing

* FeatureTypes
2022-12-31 11:22:05 +01:00
Alexander Rose
0e197b1885 Merge pull request #683 from molstar/misc
Misc tweaks
2022-12-30 20:45:46 -08:00
Alexander Rose
54697ae0d5 changelog 2022-12-30 10:16:30 -08:00
Alexander Rose
7b6eb9337e add input-color picker 2022-12-29 21:51:42 -08:00
Alexander Rose
a94fb84052 add isDisabled to ParameterMappingControl 2022-12-29 21:51:27 -08:00
Alexander Rose
528aeb1873 only update clip defines if changed 2022-12-29 21:51:18 -08:00
Alexander Rose
e7b35daf45 improve structure/unit areEqual methods
- always equal when objects are identical
2022-12-29 21:51:06 -08:00
Alexander Rose
7d10971617 avoid cloning of structure repr params 2022-12-29 21:50:49 -08:00
dsehnal
3d83211503 fix dropFiles bug 2022-12-24 12:51:09 +01:00
dsehnal
cd8c8fa020 Merge branch 'master' of https://github.com/molstar/molstar 2022-12-23 14:28:16 +01:00
David Sehnal
4c03009357 Volume Quick Color Select Control (#677)
* changelog

* volume quick select control

* tweak
2022-12-22 08:53:27 +01:00
Alexander Rose
047f547863 support tags in StructureBuilder.createStructure 2022-12-21 20:25:03 -08:00
Alexander Rose
83c62c614b Merge branch 'master' of https://github.com/molstar/molstar 2022-12-21 20:24:22 -08:00
Alexander Rose
c39b3569de add escapeRegExp util 2022-12-21 20:24:06 -08:00
dsehnal
7fea62aa46 changelog 2022-12-20 14:28:07 +01:00
midlik
d816f510ea Volseg more tweaks 2 (#675)
* Volseg: Fixed not loading volume section from state

* Volseg: Double-sided meshes (still ugly)

* Mesh extension: Detection (and correction) of inverted meshes

* Meshes extension: Fixed transparent backfaces, tidy up

* Removed duplicated classes in Meshes and Volseg extensions

* Changelog
2022-12-20 14:24:31 +01:00
dsehnal
c2064aa318 3.28.0 2022-12-20 00:37:33 +01:00
dsehnal
109709ce8b Merge branch 'master' of https://github.com/molstar/molstar 2022-12-20 00:34:53 +01:00
midlik
b200725762 Volseg more tweaks (#674)
* Volseg: Fixed not loading volume section from state

* Volseg: Double-sided meshes (still ugly)

* Mesh extension: Detection (and correction) of inverted meshes
2022-12-20 00:34:40 +01:00
dsehnal
9cf34bf987 changelog 2022-12-20 00:27:00 +01:00
Alexander Rose
be3a91bb14 package updates 2022-12-19 15:21:10 -08:00
Alexander Rose
fc948d8b27 fix gpu mc use of float16 2022-12-19 14:41:12 -08:00
midlik
8774658f4e Volseg: Fixed not loading volume section from state (#672)
* Volseg: Fixed not loading volume section from state

* Volseg: Double-sided meshes (still ugly)
2022-12-19 23:19:53 +01:00
David Sehnal
0bf32148af Viewer setting for volumes and segmentations (#673) 2022-12-19 23:10:40 +01:00
David Sehnal
97d158b615 Volseg extenison tweaks (#671)
* improve currentVolume behavior

* use different iso-value if 1sigma isnt present in the data
2022-12-19 22:16:09 +01:00
Alexander Rose
b772dea188 tweak ignoreHydrogens non-polar handling (#669) 2022-12-19 21:59:55 +01:00
Alexander Rose
5c8f0b35ec changelog 2022-12-19 10:33:36 -08:00
dsehnal
1b382653f2 add volume controls to Volseg extension 2022-12-19 16:49:30 +01:00
midlik
c7cee63c97 Volumes & Segmentations extension (#668)
* Created CellStar state action

* CellStar: download metadata

* Right panel - CellStar UI

* CellStar: Lattice segmenatation and fitted PDB models

* CellStar: Support for source-database entry ID from metadata

* CellStar: Mesh segmentation

* CellStar: Switching between multiple entries

* CellStar: Changed default API URL

* UI updates

* CellStar: Clicking on meshes shows annotations...

* support info in Converted param

* support color/size in VolumeRepresentation3DHelpers.getDefaultParams

* support multi-visual volume representations

- repr can provide list of keys
- create visual for each key

* add volume segmentation support

- segmentation property
- segment loci & location

* add volume-segment color theme

* add volume segment representation

* use optional chaining

* add support for volume segmentation cif

* improve isosurface bounding-sphere

* fix segcif schema type

* CellStar: Highlighting segments on hover in the right panel

* CellStar: Using new Mol*-native segment visualization

* lint

* Segmentation volume can have custom segment labels

* CellStar: Segment labels for Mol*-native segments

* CellStar: Removed old implementation of segments

* CellStar: Rename CellStarLatticeSegmentationData2 -> CellStarLatticeSegmentationData

* CellStar: Default volume server is https://cellstar.ncbr.muni.cz

* CellStar: debugging

* CellStar: Fixed bug in LatticeSegmentation (scaling)

* CellStar: Partially savable state

* CellStar: WaitingSlider

* CellStar: Opacity changed via params

* Savable state for opacity

* CellStar: Changing UI in animations

* CellStar: Savable state for whole current UI

* CellStar: Savable state for segment labels

* CellStar: Source can be 'idr', CellStarVolumeServerConfig.DefaultServer

* CellStar: Select segment for lattice segmentation

* CellStar: Select segment, complete

* CellStar: Changes visible labels to "Volume & Segmentation"

* CellStar: Drop list with available entries

* CellStar: Volume type switching (partial)

* CellStar: Trying to set direct-volume control points

* CellStar: Volume visual switching

* Mesh extension: removed molstar-lib-imports

* CellStar: Global options

* CellStar: Updated file headers and `CHANGELOG.md`

* CellStar: UI controls disabled while executing change

* CellStar: Hidden state nodes, fixed bug with removed global state

* CellStar: Volume opacity slider

* CellStar extension renamed to Volseg

* UI tweaks

Co-authored-by: Alexander Rose <alexander.rose@weirdbyte.de>
2022-12-19 16:15:23 +01:00
Alexander Rose
ba9474fa62 add stencil related webgl state and format 2022-12-16 21:47:05 -08:00
Alexander Rose
b7ec7ea686 Merge pull request #665 from molstar/solid-impostor
Solid impostors
2022-12-16 21:41:14 -08:00
Alexander Rose
74560adb08 changelog 2022-12-16 21:20:52 -08:00
Alexander Rose
e6a303bdda Merge branch 'master' of https://github.com/molstar/molstar into solid-impostor 2022-12-16 21:19:19 -08:00
David Sehnal
7afcf0bb68 Show histogram in direct volume control point settings (#666) 2022-12-16 20:02:46 +01:00
Alexander Rose
7aae2d0616 fix impostor sphere near clipping 2022-12-15 22:06:06 -08:00
dsehnal
9255505a3b 3.27.0 2022-12-15 13:02:37 +01:00
dsehnal
6c0dfd338d changelog 2022-12-15 12:59:45 +01:00
David Sehnal
c306e988c8 Ability to show only polar hydrogens (#663) 2022-12-15 12:26:34 +01:00
Alexander Rose
86f7a8b273 add impostor solidInterior param 2022-12-14 22:50:15 -08:00
David Sehnal
2428a8efab Merge pull request #660 from molstar/struct-conn-ions-fix
Fix struct_conn bond assignment for ions
2022-12-14 10:20:22 +01:00
Alexander Rose
cba12e487f wip, solid impostor flag 2022-12-13 23:59:08 -08:00
dsehnal
e98d7931ca Fix import 2022-12-13 18:01:59 +01:00
dsehnal
f8e2d2e3d0 tweak hasStructConnRecord 2022-12-13 17:57:31 +01:00
dsehnal
4455bab6ac Fix struct_conn bond assignment for ions 2022-12-13 16:06:19 +01:00
David Sehnal
b21fb27041 Merge pull request #658 from molstar/action-params
ApplyActionControl param handling
2022-12-13 15:20:31 +01:00
dsehnal
5402ee0019 ApplyActionControl param handling 2022-12-13 15:16:19 +01:00
David Sehnal
02654ea57b Merge pull request #657 from molstar/fix-qa-assignment
Fix QualityAssessment assignment
2022-12-13 09:33:13 +01:00
dsehnal
9cd4aeabaa Merge branch 'master' of https://github.com/molstar/molstar into fix-qa-assignment 2022-12-13 09:32:42 +01:00
dsehnal
d788511a83 header 2022-12-13 09:32:20 +01:00
Alexander Rose
378b5b8096 Merge pull request #643 from arussell123/master
Add include transparent parameter for outlines
2022-12-12 21:58:49 -08:00
Alexander Rose
e26eb2f751 Merge branch 'master' into master 2022-12-12 21:58:25 -08:00
Alexander Rose
26264b15f7 tweaks 2022-12-12 21:56:34 -08:00
Alexander Rose
309e3dd981 impostor per-pixel object clipping 2022-12-12 21:51:39 -08:00
Alexander Rose
4ff5ed3b5d fix cylinder imposter cap normals 2022-12-12 21:18:37 -08:00
dsehnal
270a757756 changelog 2022-12-12 18:39:41 +01:00
dsehnal
79e4030ab4 fix qa assignment 2022-12-12 18:35:52 +01:00
David Sehnal
fd86927e04 Merge pull request #655 from MKampfrath/access-modifiers
change access modifiers
2022-12-12 16:36:38 +01:00
MKampfrath
df3a7e5037 change access modifiers 2022-12-12 16:30:40 +01:00
Alice Russell
fefc6f14c9 Change is outline enabled to is transparant outline enabled 2022-12-12 10:43:14 +00:00
Alexander Rose
923fbef0e4 remove superfluous check 2022-12-11 20:45:26 -08:00
Alexander Rose
601bd5ba7a improve impostor shaders
- fix sphere near-clipping with orthographic projection
- fix cylinder near-clipping
- add interior cylinder caps
2022-12-10 21:47:16 -08:00
Alexander Rose
5798f20c41 lint fix 2022-12-10 21:41:25 -08:00
David Sehnal
f2e26f91a8 Merge pull request #647 from dwilliams-nobias/animation-once-stops
Animation "once" fails to stop for many frames under certain conditions
2022-12-08 16:53:27 +01:00
David Sehnal
e2cdc45be6 Merge branch 'master' into animation-once-stops 2022-12-08 16:53:15 +01:00
Alice Russell
96493bc75e Fix linting 2022-12-07 14:26:48 +00:00
Alice Russell
d78ad4a78e Merge remote-tracking branch 'upstream/master' 2022-12-07 12:25:22 +00:00
Alice Russell
0c54b0dd6e Add option to toogle transparent outline 2022-12-07 12:21:31 +00:00
Alexander Rose
65310e52de guard against issue with bumpiness in impostors 2022-12-06 22:48:22 -08:00
David Williams
285d3cd9b8 Requested changes for pull request 2022-12-06 17:36:48 -08:00
David Williams
10486abb64 Add example molecule with 1,000 animation frames 2022-12-06 17:32:44 -08:00
David Williams
8c1a9fc988 Correctly check for duration "once" for fixed or computed duration 2022-12-06 16:57:27 -08:00
Alexander Rose
7bdd0b470b 3.26.0 2022-12-04 12:18:09 -08:00
Alexander Rose
b7a51f12bf changelog 2022-12-04 12:12:22 -08:00
Alexander Rose
e002ac5474 Merge pull request #644 from molstar/power-pref
support power-preference webgl attribute
2022-12-04 12:09:46 -08:00
Alexander Rose
211d339ce0 Merge branch 'master' into power-pref 2022-12-04 12:09:39 -08:00
Alexander Rose
509fb14473 Merge pull request #645 from molstar/structure-mol-surf
add structure molecular surface visual
2022-12-04 12:09:14 -08:00
Alexander Rose
ef838a1b83 Merge branch 'master' into structure-mol-surf 2022-12-04 12:09:05 -08:00
Alexander Rose
f84b5b633d improve Representation typing 2022-12-04 09:45:12 -08:00
Alexander Rose
8ec8c1170d comment 2022-12-04 09:32:02 -08:00
David Sehnal
920b98e4d1 Merge pull request #604 from molstar/volume-theme
by-volume-value theme
2022-12-04 09:18:32 +01:00
dsehnal
c80f52d4bc Merge branch 'master' of https://github.com/molstar/molstar into volume-theme 2022-12-04 09:14:41 +01:00
dsehnal
0b6aa42daf tweaks 2022-12-04 09:14:06 +01:00
Alexander Rose
04dc6427ef update SaccharideNames 2022-12-03 22:00:24 -08:00
Alexander Rose
77e366b484 schema updates 2022-12-03 21:58:04 -08:00
Alexander Rose
add7cfa0f2 package updates 2022-12-03 21:53:00 -08:00
Alexander Rose
14d4fa142c changelog 2022-12-03 21:46:55 -08:00
Alexander Rose
7fe1617f25 shadow param tweaks 2022-12-03 21:46:30 -08:00
Alexander Rose
cbe4cb521c add structure molecular surface visual 2022-12-03 21:32:54 -08:00
Alexander Rose
7c394525c1 Merge pull request #637 from molstar/sss
Screen-space shadows
2022-12-03 10:59:05 -08:00
Alexander Rose
12ed051fea Merge pull request #606 from jpattle/remove-caps-from-ligand-query
Remove common protein caps from ligand query
2022-12-03 10:41:00 -08:00
Alexander Rose
014913c635 support power-preference webgl attribute 2022-12-03 10:17:57 -08:00
Alexander Rose
369e45c282 handle bounds in shadow screen fade 2022-12-03 10:02:14 -08:00
Alice Russell
2892caec0c Update changelog 2022-12-02 12:31:56 +00:00
Alice Russell
eb609e918a linting fixes 2022-12-02 12:28:51 +00:00
Alice Russell
9217e58845 Add option to set minimum alpha value for outlines to be shown 2022-12-02 12:12:22 +00:00
Alexander Rose
26b633618a tweak cellpack shadow params 2022-11-27 22:22:29 -08:00
Alexander Rose
e3054d6ee1 shadows bonds check 2022-11-27 22:07:23 -08:00
Alexander Rose
0b6294f4ec check for shadow in PostprocessingPass.isEnabled 2022-11-26 22:33:11 -08:00
Alexander Rose
7b130dc0f3 cleanup 2022-11-26 22:09:24 -08:00
Alexander Rose
d93c128c25 cleanup 2022-11-26 22:04:41 -08:00
Alexander Rose
e1dd74775e refactor sss
- only sss, remove other ao implementations
- tweak params
- make sss a separate renderable
2022-11-26 21:47:08 -08:00
Alexander Rose
e61e706607 Merge branch 'master' of https://github.com/molstar/molstar into fake_shadow 2022-11-25 21:13:35 -08:00
jpattle
a57d46deaa Merge branch 'master' into remove-caps-from-ligand-query 2022-11-21 08:58:41 +00:00
Alexander Rose
60efdc0071 3.25.1 2022-11-20 10:43:31 -08:00
Alexander Rose
4dc1155a9e changelog 2022-11-20 10:38:07 -08:00
Alexander Rose
5cabe6fb42 fix auto structure-quality for coarse models 2022-11-20 10:37:30 -08:00
Alexander Rose
42239c305f better handle single-element units in Structure.eachUnitPair 2022-11-20 10:25:26 -08:00
ludovic autin
fd3c763349 All parameters exposed to try to find best parameters for different representation. 2022-11-18 09:13:42 -08:00
ludovic autin
daa9bbf2c9 fix trailing space 2022-11-17 09:31:21 -08:00
ludovic autin
c7dad00908 fake screen space shadow, second pass ssao 2022-11-17 09:28:00 -08:00
ludovic autin
24317717e8 Merge remote-tracking branch 'upstream/master' into forkdev 2022-11-16 14:01:04 -08:00
dsehnal
dd3eac6db6 3.25.0 2022-11-16 00:15:41 +01:00
dsehnal
1606f8517f changelog 2022-11-16 00:12:57 +01:00
David Sehnal
3a98401ada Merge pull request #623 from JonStargaryen/master
Fix Handling of Gzipped Assets by Reverting #615
2022-11-16 00:11:02 +01:00
Alexander Rose
4764251241 operator key tweaks 2022-11-14 21:48:44 -08:00
Sebastian Bittrich
3de04b7b9d CHANGELOG 2022-11-14 13:17:15 -08:00
Sebastian Bittrich
04908495e9 Revert "keep asset url to detect compression"
This reverts commit 2bc45c25fe.
2022-11-14 13:13:06 -08:00
Alexander Rose
3c835c848e 3.24.0 2022-11-13 20:56:58 -08:00
Alexander Rose
0f1788f122 changelog 2022-11-13 20:51:24 -08:00
Alexander Rose
3d72e700a4 package updates 2022-11-13 20:48:33 -08:00
Alexander Rose
a5cf41e65f fix viewport color for transparent background (#617) 2022-11-13 20:47:30 -08:00
Alexander Rose
7029bc41d7 Merge pull request #621 from molstar/theme-strength
add theme strength to 3d repr state
2022-11-13 20:22:14 -08:00
Alexander Rose
b87d40c844 changelog 2022-11-13 20:21:41 -08:00
Alexander Rose
9e154376d3 Merge branch 'master' of https://github.com/molstar/molstar into theme-strength 2022-11-13 20:18:56 -08:00
Alexander Rose
4a9fdbce57 Merge pull request #619 from molstar/opkey
add key to symmetry operator
2022-11-13 20:18:12 -08:00
Alexander Rose
775e335292 remove cantor-pair use 2022-11-13 20:11:42 -08:00
Alexander Rose
e553bf4deb add theme strength to 3d repr state 2022-11-12 21:58:13 -08:00
Alexander Rose
db0e09ec6e add more webgl extensions
- draw_buffers_indexed
- parallel_shader_compile
- fbo_render_mipmap
2022-11-12 20:34:18 -08:00
Alexander Rose
ba50760f92 add key to symmetry operator
- molql operatorKey
- index-pair operator prop
2022-11-12 20:30:33 -08:00
David Sehnal
524e6d4f81 Merge pull request #615 from JonStargaryen/master
keep asset url to detect compression
2022-11-11 07:47:44 +01:00
ludovic autin
db93a669ab Merge branch 'molstar:master' into forkdev 2022-11-10 13:59:26 -08:00
Sebastian Bittrich
423f5b0502 CHANGELOG 2022-11-10 13:16:27 -08:00
Sebastian Bittrich
2bc45c25fe keep asset url to detect compression 2022-11-10 13:13:49 -08:00
Jason Pattle
d341463f67 Removed unneccessary wholeResidues query from new ligand selection query as the atomGroups residue-test already covers this functionality 2022-11-07 08:37:16 +00:00
Alexander Rose
19d1ecb36a add (slow) texture-mesh position iterator 2022-11-06 23:27:17 -08:00
Alexander Rose
ffe4047f97 fix vertex color/overpaint in direct-volume shader 2022-11-06 22:24:36 -08:00
Alexander Rose
15a3c29e7a adjust external-volume theme file name 2022-11-06 22:23:15 -08:00
Jason Pattle
42ff593004 Added missing semicolon; linting 2022-11-02 10:06:26 +00:00
Jason Pattle
5140af4e6f Added myself as a contributor to package.json 2022-11-02 09:56:07 +00:00
Jason Pattle
0f5b4c00a9 Updated changelog 2022-11-02 09:54:57 +00:00
Jason Pattle
feb69f4987 Excluded whole residues which match any common protein caps from the ligand selection query 2022-11-02 09:49:44 +00:00
Jason Pattle
84eb9a58ca Added names for common protein caps 2022-11-02 09:49:07 +00:00
dsehnal
fbd96f473a External Volume theme features 2022-11-01 11:54:27 +01:00
dsehnal
69228157dc fix typo 2022-10-31 15:42:13 +01:00
dsehnal
6808f32b8d by-volume-value theme 2022-10-31 15:41:10 +01:00
ludovic autin
9e69f5dcfa Merge branch 'master' of https://github.com/corredD/molstar-proto into forkdev
# Conflicts:
#	CHANGELOG.md
2022-07-05 08:51:25 -07:00
ludovic autin
409ed9ad67 Merge branch 'forkdev' of https://github.com/corredD/molstar-proto into forkdev
# Conflicts:
#	CHANGELOG.md
2022-06-28 08:11:29 -07:00
ludovic autin
06e78e19d0 Merge branch 'forkdev' of https://github.com/corredD/molstar-proto into forkdev 2022-05-31 09:55:03 -07:00
ludovic autin
9a1d746170 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-11-17 10:06:49 -08:00
ludovic autin
ef87ce3507 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-11-10 14:35:24 -08:00
ludovic autin
2b9053eac4 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-11-01 10:21:08 -07:00
ludovic autin
116ef719be Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-09-20 09:17:09 -07:00
322 changed files with 43842 additions and 4063 deletions

View File

@@ -18,7 +18,7 @@
],
"brace-style": "off",
"comma-spacing": "off",
"space-infix-ops": "error",
"space-infix-ops": "off",
"comma-dangle": "off",
"eqeqeq": [
"error",
@@ -107,6 +107,7 @@
"1tbs", { "allowSingleLine": true }
],
"@typescript-eslint/comma-spacing": "error",
"@typescript-eslint/space-infix-ops": "error",
"@typescript-eslint/space-before-function-paren": ["error", {
"anonymous": "always",
"named": "never",

View File

@@ -1,3 +1,5 @@
name: Build
on:
push:
pull_request:
@@ -15,6 +17,6 @@ jobs:
- name: Lint
run: npm run lint
- name: Test
run: npm install --no-save "gl@^5.0.0" && xvfb-run --auto-servernum npm run jest
run: npm install --no-save "gl@^6.0.2" && xvfb-run --auto-servernum npm run jest
- name: Build
run: npm run build

View File

@@ -1,18 +0,0 @@
language: node_js
os: linux
sudo: required
dist: trusty
before_install:
- sudo apt-get install -y mesa-utils
- sudo apt-get install -y xvfb
- sudo apt-get install -y libgl1-mesa-dri
- sudo apt-get install -y libglapi-mesa
- sudo apt-get install -y libosmesa6
- sudo apt-get install -y gcc-4.9
- sudo apt-get install -y libstdc++6
- sudo apt-get install -y libxi-dev
node_js:
- "12"
- "10"
before_script:
- export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start

View File

@@ -6,18 +6,122 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
- Improve Component camera focus based on the PCA of the structure and the following rules:
- The first residue should be in first quadrant if there is only one chain
- The average position of the residues of the first chain should be in the first quadrant if there is more than one chain
- Add `HeadlessPluginContext` and `HeadlessScreenshotHelper` to be used in Node.js
- Add example `image-renderer`
- Fix wrong offset when rendering text with orthographic projectio
n
- Update camera/handle helper when `devicePixelRatio` changes
- Add various options to customize the axes camera-helper
- Fix issue with texture-mesh color smoothing when changing themes
- Add fast boundary helper and corresponding unit trait
- Add Observable for Canvas3D commits
## [v3.30.0] - 2023-01-29
- Improve `Dnatco` extension
- Factor out common code in `Dnatco` extension
- Add `NtC tube` visual. Applicable for structures with NtC annotation
- [Breaking] Rename `DnatcoConfalPyramids` to `DnatcoNtCs`
- Improve boundary calculation performance
- Add option to create & include images in state snapshots
- Fix SSAO artefacts with high bias values
- Fix SSAO resolution scale parameter handling
- Improve outlines, visually more stable at different view distances
## [v3.29.0] - 2023-01-15
- `meshes` extension: Fixed a bug in mesh visualization (show backfaces when opacity < 1)
- Add color quick select control to Volume controls
- Fix `dropFiles` bug
- Fix some cyclic imports and reduce the use of const enums. This should make it easier to use the library with the `isolatedModules: true` TS config.
- Fix `dropFiles` bug (#679)
- Add `input type='color'` picker to `CombinedColorControl`
- Set `ParameterMappingControl` disabled when state is updating
- Performance tweaks
- Update clip `defines` only when changed
- Check for identity in structure/unit areEqual methods
- Avoid cloning of structure representation parameters
- Make SymmetryOperator.createMapping monomorphic
- Improve bonding-sphere calculation
- Defer Scene properties calculation (markerAverage, opacityAverage, hasOpaque)
- Improve checks in in UnitsRepresentation setVisualState
- Add StructureElement.Loci.forEachLocation
- Add RepresentationRegistry.clear and ThemeRegistry.clear
- Add generic Loci support for overpaint, substance, clipping themes
- Add `.getCenter` and `.center` to `Camera`
- Add support to dim unmarked groups
- Add support for marker edge strength
## [v3.28.0] - 2022-12-20
- Show histogram in direct volume control point settings
- Add `solidInterior` parameter to sphere/cylinder impostors
- [Breaking] Tweak `ignoreHydrogens` non-polar handling (introduced in 3.27.0)
- Add `meshes` and `volumes-and-segmentations` extensions
- See https://molstarvolseg.ncbr.muni.cz/ for more info
- Fix missing support for info in `ParamDefinition.Converted`
- Add support for multi-visual volume representations
- Improve volume isosurface bounding-sphere
- Add basic volume segmentation support to core
- Add `Volume.Segment` model
- Add `Segmentation` custom volume property
- Add `SegmentRepresentation` representation
- Add `volume-segment` color theme
- Fix GPU marching cubes failing for large meshes with webgl2 (due to use of float16)
## [v3.27.0] - 2022-12-15
- Add an `includeTransparent` parameter to hide/show outlines of components that are transparent
- Fix 'once' for animations of systems with many frames
- Better guard against issue (black fringes) with bumpiness in impostors
- Improve impostor shaders
- Fix sphere near-clipping with orthographic projection
- Fix cylinder near-clipping
- Add interior cylinder caps
- Add per-pixel object clipping
- Fix `QualityAssessment` assignment bug for structures with different auth vs label sequence numbering
- Refresh `ApplyActionControl`'s param definition when toggling expanded state
- Fix `struct_conn` bond assignment for ions
- Ability to show only polar hydrogens
## [v3.26.0] - 2022-12-04
- Support for ``powerPreference`` webgl attribute. Add ``PluginConfig.General.PowerPreference`` and ``power-preference`` Viewer GET param.
- Excluded common protein caps `NME` and `ACE` from the ligand selection query
- Add screen-space shadow post-processing effect
- Add "Structure Molecular Surface" visual
- Add `external-volume` theme (coloring of arbitrary geometries by user-selected volume)
## [v3.25.1] - 2022-11-20
- Fix edge-case in `Structure.eachUnitPair` with single-element units
- Fix 'auto' structure-quality for coarse models
## [v3.25.0] - 2022-11-16
- Fix handling of gzipped assets (reverts #615)
## [v3.24.0] - 2022-11-13
- Make `PluginContext.initContainer` checkered canvas background optional
- Store URL of downloaded assets to detect zip/gzip based on extension (#615)
- Add optional `operator.key`; can be referenced in `IndexPairBonds`
- Add overpaint/transparency/substance theme strength to representations
- Fix viewport color for transparent background
## [v3.23.0] - 2022-10-19
- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
- Add `PluginContext.canvas3dInitialized`
- `createPluginUI` now resolves after the 3d canvas has been initialized
- Change EM Volume Streaming default from `Whote Structure` to `Auto`
- Change EM Volume Streaming default from `Whole Structure` to `Auto`
## [v3.22.0] - 2022-10-17
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
## [v3.21.0] - 2022-10-17

View File

@@ -1,6 +1,6 @@
[![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](./LICENSE)
[![npm version](https://badge.fury.io/js/molstar.svg)](https://www.npmjs.com/package/molstar)
[![Build Status](https://travis-ci.org/molstar/molstar.svg?branch=master)](https://travis-ci.org/molstar/molstar)
[![Build](https://github.com/molstar/molstar/actions/workflows/node.yml/badge.svg)](https://github.com/molstar/molstar/actions/workflows/node.yml)
[![Gitter](https://badges.gitter.im/molstar/Lobby.svg)](https://gitter.im/molstar/Lobby)
# Mol*

28000
examples/long_animation.sdf Normal file

File diff suppressed because it is too large Load Diff

7870
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.23.0",
"version": "3.31.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -13,7 +13,7 @@
"scripts": {
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm install --no-save \"gl@^5.0.0\" && npm run lint && jest",
"test": "npm install --no-save \"gl@^6.0.2\" && npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"clean": "node ./scripts/clean.js",
@@ -92,57 +92,62 @@
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
"Adam Midlik <midlik@gmail.com>",
"Koya Sakuma <koya.sakuma.work@gmail.com>",
"Gianluca Tomasello <giagitom@gmail.com>"
"Gianluca Tomasello <giagitom@gmail.com>",
"Ke Ma <mark.ma@rcsb.org>",
"Jason Pattle <jpattle@exscientia.co.uk>",
"David Williams <dwilliams@nobiastx.com>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.2.1",
"@graphql-codegen/cli": "^2.13.7",
"@graphql-codegen/time": "^3.2.1",
"@graphql-codegen/typescript": "^2.7.4",
"@graphql-codegen/add": "^4.0.0",
"@graphql-codegen/cli": "^3.0.0",
"@graphql-codegen/time": "^4.0.0",
"@graphql-codegen/typescript": "^3.0.0",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
"@graphql-codegen/typescript-graphql-request": "^4.5.6",
"@graphql-codegen/typescript-operations": "^2.5.4",
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.1",
"@types/jest": "^29.1.2",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"@graphql-codegen/typescript-graphql-request": "^4.5.8",
"@graphql-codegen/typescript-operations": "^3.0.0",
"@types/cors": "^2.8.13",
"@types/gl": "^6.0.2",
"@types/jpeg-js": "^0.3.7",
"@types/pngjs": "^6.0.1",
"@types/jest": "^29.4.0",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"benchmark": "^2.1.4",
"concurrently": "^7.4.0",
"concurrently": "^7.6.0",
"cpx2": "^4.2.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"eslint": "^8.25.0",
"css-loader": "^6.7.3",
"eslint": "^8.33.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.1.0",
"fs-extra": "^11.1.0",
"graphql": "^16.6.0",
"http-server": "^14.1.1",
"jest": "^29.2.0",
"mini-css-extract-plugin": "^2.6.1",
"jest": "^29.4.1",
"mini-css-extract-plugin": "^2.7.2",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.55.0",
"sass-loader": "^13.1.0",
"simple-git": "^3.14.1",
"sass": "^1.58.0",
"sass-loader": "^13.2.0",
"simple-git": "^3.16.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
"ts-jest": "^29.0.5",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"dependencies": {
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.2",
"@types/compression": "1.7.2",
"@types/express": "^4.17.14",
"@types/node": "^16.11.66",
"@types/express": "^4.17.17",
"@types/node": "^16.18.12",
"@types/node-fetch": "^2.6.2",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
@@ -151,17 +156,22 @@
"cors": "^2.8.5",
"express": "^4.18.2",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.15",
"immutable": "^4.1.0",
"node-fetch": "^2.6.7",
"rxjs": "^7.5.7",
"swagger-ui-dist": "^4.14.3",
"tslib": "^2.4.0",
"immer": "^9.0.19",
"immutable": "^4.2.3",
"node-fetch": "^2.6.9",
"rxjs": "^7.8.0",
"swagger-ui-dist": "^4.15.5",
"tslib": "^2.5.0",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"
},
"peerDependencies": {
"react": "^18.1.0 || ^17.0.2 || ^16.14.0",
"react-dom": "^18.1.0 || ^17.0.2 || ^16.14.0"
},
"optionalDependencies": {
"gl": "^6.0.2",
"jpeg-js": "^0.4.4",
"pngjs": "^6.0.0"
}
}

View File

@@ -31,7 +31,8 @@ function shinyStyle(plugin: PluginContext) {
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'off', params: {} },
outline: { name: 'off', params: {} }
shadow: { name: 'off', params: {} },
outline: { name: 'off', params: {} },
}
} });
}
@@ -48,13 +49,15 @@ function occlusionStyle(plugin: PluginContext) {
blurKernelSize: 15,
radius: 5,
samples: 32,
resolutionScale: 1
resolutionScale: 1,
} },
outline: { name: 'on', params: {
scale: 1.0,
threshold: 0.33,
color: Color(0x0000),
} }
includeTransparent: true,
} },
shadow: { name: 'off', params: {} },
}
} });
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,8 +7,9 @@
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { DnatcoNtCs } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Volseg, VolsegVolumeServerConfig } from '../../extensions/volumes-and-segmentations';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
@@ -49,16 +50,17 @@ import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/car
import { Backgrounds } from '../../extensions/backgrounds';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
'volseg': PluginSpec.Behavior(Volseg),
'backgrounds': PluginSpec.Behavior(Backgrounds),
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
@@ -91,6 +93,7 @@ const DefaultViewerOptions = {
enableDpoit: PluginConfig.General.EnableDpoit.defaultValue,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -104,6 +107,7 @@ const DefaultViewerOptions = {
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
@@ -163,6 +167,7 @@ export class Viewer {
[PluginConfig.General.EnableDpoit, o.enableDpoit],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
[PluginConfig.General.PowerPreference, o.powerPreference],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
@@ -177,6 +182,7 @@ export class Viewer {
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
]
};

View File

@@ -63,6 +63,7 @@
var enableDpoit = getParam('enable-dpoit', '[^&]+').trim() === '1';
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
molstar.Viewer.create('app', {
layoutShowControls: !hideControls,
@@ -80,6 +81,7 @@
enableDpoit: enableDpoit ? true : void 0,
preferWebgl1: preferWebgl1,
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
powerPreference: powerPreference || 'high-performance',
}).then(viewer => {
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);

View File

@@ -152,7 +152,7 @@ function getImportFrames(d: Data.CifFrame, imports: Imports) {
}
/** get field from given or linked category */
function getField(category: string, field: string, d: Data.CifFrame, imports: Imports, ctx: FrameData): Data.CifField|undefined {
function getField(category: string, field: string, d: Data.CifFrame, imports: Imports, ctx: FrameData): Data.CifField | undefined {
const { categories, links } = ctx;
const cat = d.categories[category];
if (cat) {

View File

@@ -25,7 +25,7 @@ async function readFile(path: string) {
}
}
async function parseCif(data: string|Uint8Array) {
async function parseCif(data: string | Uint8Array) {
const comp = CIF.parse(data);
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
if (parsed.isError) throw parsed;

View File

@@ -38,7 +38,7 @@ function print(volume: Volume) {
}
async function doMesh(volume: Volume, filename: string) {
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
// Export the mesh in OBJ format.

View File

@@ -25,7 +25,7 @@ import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
import { setDebugMode, setTimingMode } from '../../mol-util/debug';
import { setDebugMode, setTimingMode, consoleStats } from '../../mol-util/debug';
interface DemoInput {
moleculeSdf: string,
@@ -222,4 +222,5 @@ export class AlphaOrbitalsExample {
(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
(window as any).AlphaOrbitalsExample.setDebugMode = setDebugMode;
(window as any).AlphaOrbitalsExample.setTimingMode = setTimingMode;
(window as any).AlphaOrbitalsExample.setTimingMode = setTimingMode;
(window as any).AlphaOrbitalsExample.consoleStats = consoleStats;

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*
* Example command-line application generating images of PDB structures
* Build: npm install --no-save gl jpeg-js pngjs // these packages are not listed in dependencies for performance reasons
* npm run build
* Run: node lib/commonjs/examples/image-renderer 1cbs ../outputs_1cbs/
*/
import { ArgumentParser } from 'argparse';
import fs from 'fs';
import path from 'path';
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
import { ModelFromTrajectory, StructureComponent, StructureFromModel, TrajectoryFromMmCif } from '../../mol-plugin-state/transforms/model';
import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { STYLIZED_POSTPROCESSING } from '../../mol-plugin/util/headless-screenshot';
interface Args {
pdbId: string,
outDirectory: string
}
function parseArguments(): Args {
const parser = new ArgumentParser({ description: 'Example command-line application generating images of PDB structures' });
parser.add_argument('pdbId', { help: 'PDB identifier' });
parser.add_argument('outDirectory', { help: 'Directory for outputs' });
const args = parser.parse_args();
return { ...args };
}
async function main() {
const args = parseArguments();
const url = `https://www.ebi.ac.uk/pdbe/entry-files/download/${args.pdbId}.bcif`;
console.log('PDB ID:', args.pdbId);
console.log('Source URL:', url);
console.log('Outputs:', args.outDirectory);
// Create a headless plugin
const plugin = new HeadlessPluginContext(DefaultPluginSpec(), { width: 800, height: 800 });
await plugin.init();
// Download and visualize data in the plugin
const update = plugin.build();
const structure = update.toRoot()
.apply(Download, { url, isBinary: true })
.apply(ParseCif)
.apply(TrajectoryFromMmCif)
.apply(ModelFromTrajectory)
.apply(StructureFromModel);
const polymer = structure.apply(StructureComponent, { type: { name: 'static', params: 'polymer' } });
const ligand = structure.apply(StructureComponent, { type: { name: 'static', params: 'ligand' } });
polymer.apply(StructureRepresentation3D, {
type: { name: 'cartoon', params: { alpha: 1 } },
colorTheme: { name: 'sequence-id', params: {} },
});
ligand.apply(StructureRepresentation3D, {
type: { name: 'ball-and-stick', params: { sizeFactor: 1 } },
colorTheme: { name: 'element-symbol', params: { carbonColor: { name: 'element-symbol', params: {} } } },
sizeTheme: { name: 'physical', params: {} },
});
await update.commit();
// Export images
fs.mkdirSync(args.outDirectory, { recursive: true });
await plugin.saveImage(path.join(args.outDirectory, 'basic.png'));
await plugin.saveImage(path.join(args.outDirectory, 'basic.jpg'));
await plugin.saveImage(path.join(args.outDirectory, 'large.png'), { width: 1600, height: 1200 });
await plugin.saveImage(path.join(args.outDirectory, 'large.jpg'), { width: 1600, height: 1200 });
await plugin.saveImage(path.join(args.outDirectory, 'stylized.png'), undefined, STYLIZED_POSTPROCESSING);
await plugin.saveImage(path.join(args.outDirectory, 'stylized.jpg'), undefined, STYLIZED_POSTPROCESSING);
await plugin.saveImage(path.join(args.outDirectory, 'stylized-compressed-jpg.jpg'), undefined, STYLIZED_POSTPROCESSING, undefined, 10);
// Export state loadable in Mol* Viewer
await plugin.saveStateSnapshot(path.join(args.outDirectory, 'molstar-state.molj'));
// Cleanup
await plugin.clear();
plugin.dispose();
}
main();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -25,7 +25,8 @@ const Canvas3DPresets = {
canvas3d: <Preset>{
postprocessing: {
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000), includeTransparent: true, } },
shadow: { name: 'off', params: {} },
},
renderer: {
ambientIntensity: 1.0,
@@ -37,7 +38,8 @@ const Canvas3DPresets = {
canvas3d: <Preset>{
postprocessing: {
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
outline: { name: 'off', params: {} }
outline: { name: 'off', params: {} },
shadow: { name: 'off', params: {} },
},
renderer: {
ambientIntensity: 0.4,
@@ -50,7 +52,8 @@ const Canvas3DPresets = {
canvas3d: <Preset>{
postprocessing: {
occlusion: { name: 'off', params: {} },
outline: { name: 'off', params: {} }
outline: { name: 'off', params: {} },
shadow: { name: 'off', params: {} },
},
renderer: {
ambientIntensity: 0.4,

View File

@@ -52,7 +52,7 @@ export interface Compartment {
}
// Primitives discribing a compartment
export const enum CompartmentPrimitiveType {
export enum CompartmentPrimitiveType {
MetaBall = 0,
Sphere = 1,
Cube = 2,

View File

@@ -236,7 +236,7 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
structure = await getCurve(name, getCurveTransforms(ingredient), model);
} else {
if ((!results || results.length === 0)) return;
let bu: string|undefined = source.bu ? source.bu : undefined;
let bu: string | undefined = source.bu ? source.bu : undefined;
if (bu) {
if (bu === 'AU') {
bu = undefined;
@@ -585,7 +585,7 @@ export const LoadCellPackModel = StateAction.build({
... ctx.managers.structure.component.state.options,
visualQuality: 'custom',
ignoreLight: true,
showHydrogens: false,
hydrogens: 'hide-all',
});
ctx.canvas3d?.setProps({
multiSample: { mode: 'off' },
@@ -606,12 +606,22 @@ export const LoadCellPackModel = StateAction.build({
resolutionScale: 1,
}
},
shadow: {
name: 'on',
params: {
bias: 0.6,
maxDistance: 80,
steps: 3,
tolerance: 1.0,
}
},
outline: {
name: 'on',
params: {
scale: 1,
threshold: 0.33,
color: ColorNames.black,
includeTransparent: true,
}
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ConfalPyramidsPreset } from './confal-pyramids/behavior';
import { ConfalPyramidsColorThemeProvider } from './confal-pyramids/color';
import { ConfalPyramidsProvider } from './confal-pyramids/property';
import { ConfalPyramidsRepresentationProvider } from './confal-pyramids/representation';
import { NtCTubePreset } from './ntc-tube/behavior';
import { NtCTubeColorThemeProvider } from './ntc-tube/color';
import { NtCTubeProvider } from './ntc-tube/property';
import { NtCTubeRepresentationProvider } from './ntc-tube/representation';
export const DnatcoNtCs = PluginBehavior.create<{ autoAttach: boolean, showToolTip: boolean }>({
name: 'dnatco-ntcs',
category: 'custom-props',
display: {
name: 'DNATCO NtC Annotations',
description: 'DNATCO NtC Annotations',
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showToolTip: boolean }> {
register(): void {
this.ctx.customModelProperties.register(ConfalPyramidsProvider, this.params.autoAttach);
this.ctx.customModelProperties.register(NtCTubeProvider, this.params.autoAttach);
this.ctx.representation.structure.themes.colorThemeRegistry.add(ConfalPyramidsColorThemeProvider);
this.ctx.representation.structure.registry.add(ConfalPyramidsRepresentationProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.add(NtCTubeColorThemeProvider);
this.ctx.representation.structure.registry.add(NtCTubeRepresentationProvider);
this.ctx.builders.structure.representation.registerPreset(ConfalPyramidsPreset);
this.ctx.builders.structure.representation.registerPreset(NtCTubePreset);
}
unregister() {
this.ctx.customModelProperties.unregister(ConfalPyramidsProvider.descriptor.name);
this.ctx.customModelProperties.unregister(NtCTubeProvider.descriptor.name);
this.ctx.representation.structure.registry.remove(ConfalPyramidsRepresentationProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(ConfalPyramidsColorThemeProvider);
this.ctx.representation.structure.registry.remove(NtCTubeRepresentationProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(NtCTubeColorThemeProvider);
this.ctx.builders.structure.representation.unregisterPreset(ConfalPyramidsPreset);
this.ctx.builders.structure.representation.unregisterPreset(NtCTubePreset);
}
},
params: () => ({
autoAttach: PD.Boolean(true),
showToolTip: PD.Boolean(true)
})
});

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { Color, ColorMap } from '../../mol-util/color';
export const DefaultNtCClassColors = {
A: 0xFFC1C1,
B: 0xC8CFFF,
BII: 0x0059DA,
miB: 0x3BE8FB,
Z: 0x01F60E,
IC: 0xFA5CFB,
OPN: 0xE90000,
SYN: 0xFFFF01,
N: 0xF2F2F2,
};
export const ErrorColor = Color(0xFFA10A);
export const NtCColors = ColorMap({
NANT_Upr: DefaultNtCClassColors.N,
NANT_Lwr: DefaultNtCClassColors.N,
AA00_Upr: DefaultNtCClassColors.A,
AA00_Lwr: DefaultNtCClassColors.A,
AA02_Upr: DefaultNtCClassColors.A,
AA02_Lwr: DefaultNtCClassColors.A,
AA03_Upr: DefaultNtCClassColors.A,
AA03_Lwr: DefaultNtCClassColors.A,
AA04_Upr: DefaultNtCClassColors.A,
AA04_Lwr: DefaultNtCClassColors.A,
AA08_Upr: DefaultNtCClassColors.A,
AA08_Lwr: DefaultNtCClassColors.A,
AA09_Upr: DefaultNtCClassColors.A,
AA09_Lwr: DefaultNtCClassColors.A,
AA01_Upr: DefaultNtCClassColors.A,
AA01_Lwr: DefaultNtCClassColors.A,
AA05_Upr: DefaultNtCClassColors.A,
AA05_Lwr: DefaultNtCClassColors.A,
AA06_Upr: DefaultNtCClassColors.A,
AA06_Lwr: DefaultNtCClassColors.A,
AA10_Upr: DefaultNtCClassColors.A,
AA10_Lwr: DefaultNtCClassColors.A,
AA11_Upr: DefaultNtCClassColors.A,
AA11_Lwr: DefaultNtCClassColors.A,
AA07_Upr: DefaultNtCClassColors.A,
AA07_Lwr: DefaultNtCClassColors.A,
AA12_Upr: DefaultNtCClassColors.A,
AA12_Lwr: DefaultNtCClassColors.A,
AA13_Upr: DefaultNtCClassColors.A,
AA13_Lwr: DefaultNtCClassColors.A,
AB01_Upr: DefaultNtCClassColors.A,
AB01_Lwr: DefaultNtCClassColors.B,
AB02_Upr: DefaultNtCClassColors.A,
AB02_Lwr: DefaultNtCClassColors.B,
AB03_Upr: DefaultNtCClassColors.A,
AB03_Lwr: DefaultNtCClassColors.B,
AB04_Upr: DefaultNtCClassColors.A,
AB04_Lwr: DefaultNtCClassColors.B,
AB05_Upr: DefaultNtCClassColors.A,
AB05_Lwr: DefaultNtCClassColors.B,
BA01_Upr: DefaultNtCClassColors.B,
BA01_Lwr: DefaultNtCClassColors.A,
BA05_Upr: DefaultNtCClassColors.B,
BA05_Lwr: DefaultNtCClassColors.A,
BA09_Upr: DefaultNtCClassColors.B,
BA09_Lwr: DefaultNtCClassColors.A,
BA08_Upr: DefaultNtCClassColors.BII,
BA08_Lwr: DefaultNtCClassColors.A,
BA10_Upr: DefaultNtCClassColors.B,
BA10_Lwr: DefaultNtCClassColors.A,
BA13_Upr: DefaultNtCClassColors.BII,
BA13_Lwr: DefaultNtCClassColors.A,
BA16_Upr: DefaultNtCClassColors.BII,
BA16_Lwr: DefaultNtCClassColors.A,
BA17_Upr: DefaultNtCClassColors.BII,
BA17_Lwr: DefaultNtCClassColors.A,
BB00_Upr: DefaultNtCClassColors.B,
BB00_Lwr: DefaultNtCClassColors.B,
BB01_Upr: DefaultNtCClassColors.B,
BB01_Lwr: DefaultNtCClassColors.B,
BB17_Upr: DefaultNtCClassColors.B,
BB17_Lwr: DefaultNtCClassColors.B,
BB02_Upr: DefaultNtCClassColors.B,
BB02_Lwr: DefaultNtCClassColors.B,
BB03_Upr: DefaultNtCClassColors.B,
BB03_Lwr: DefaultNtCClassColors.B,
BB11_Upr: DefaultNtCClassColors.B,
BB11_Lwr: DefaultNtCClassColors.B,
BB16_Upr: DefaultNtCClassColors.B,
BB16_Lwr: DefaultNtCClassColors.B,
BB04_Upr: DefaultNtCClassColors.B,
BB04_Lwr: DefaultNtCClassColors.BII,
BB05_Upr: DefaultNtCClassColors.B,
BB05_Lwr: DefaultNtCClassColors.BII,
BB07_Upr: DefaultNtCClassColors.BII,
BB07_Lwr: DefaultNtCClassColors.BII,
BB08_Upr: DefaultNtCClassColors.BII,
BB08_Lwr: DefaultNtCClassColors.BII,
BB10_Upr: DefaultNtCClassColors.miB,
BB10_Lwr: DefaultNtCClassColors.miB,
BB12_Upr: DefaultNtCClassColors.miB,
BB12_Lwr: DefaultNtCClassColors.miB,
BB13_Upr: DefaultNtCClassColors.miB,
BB13_Lwr: DefaultNtCClassColors.miB,
BB14_Upr: DefaultNtCClassColors.miB,
BB14_Lwr: DefaultNtCClassColors.miB,
BB15_Upr: DefaultNtCClassColors.miB,
BB15_Lwr: DefaultNtCClassColors.miB,
BB20_Upr: DefaultNtCClassColors.miB,
BB20_Lwr: DefaultNtCClassColors.miB,
IC01_Upr: DefaultNtCClassColors.IC,
IC01_Lwr: DefaultNtCClassColors.IC,
IC02_Upr: DefaultNtCClassColors.IC,
IC02_Lwr: DefaultNtCClassColors.IC,
IC03_Upr: DefaultNtCClassColors.IC,
IC03_Lwr: DefaultNtCClassColors.IC,
IC04_Upr: DefaultNtCClassColors.IC,
IC04_Lwr: DefaultNtCClassColors.IC,
IC05_Upr: DefaultNtCClassColors.IC,
IC05_Lwr: DefaultNtCClassColors.IC,
IC06_Upr: DefaultNtCClassColors.IC,
IC06_Lwr: DefaultNtCClassColors.IC,
IC07_Upr: DefaultNtCClassColors.IC,
IC07_Lwr: DefaultNtCClassColors.IC,
OP01_Upr: DefaultNtCClassColors.OPN,
OP01_Lwr: DefaultNtCClassColors.OPN,
OP02_Upr: DefaultNtCClassColors.OPN,
OP02_Lwr: DefaultNtCClassColors.OPN,
OP03_Upr: DefaultNtCClassColors.OPN,
OP03_Lwr: DefaultNtCClassColors.OPN,
OP04_Upr: DefaultNtCClassColors.OPN,
OP04_Lwr: DefaultNtCClassColors.OPN,
OP05_Upr: DefaultNtCClassColors.OPN,
OP05_Lwr: DefaultNtCClassColors.OPN,
OP06_Upr: DefaultNtCClassColors.OPN,
OP06_Lwr: DefaultNtCClassColors.OPN,
OP07_Upr: DefaultNtCClassColors.OPN,
OP07_Lwr: DefaultNtCClassColors.OPN,
OP08_Upr: DefaultNtCClassColors.OPN,
OP08_Lwr: DefaultNtCClassColors.OPN,
OP09_Upr: DefaultNtCClassColors.OPN,
OP09_Lwr: DefaultNtCClassColors.OPN,
OP10_Upr: DefaultNtCClassColors.OPN,
OP10_Lwr: DefaultNtCClassColors.OPN,
OP11_Upr: DefaultNtCClassColors.OPN,
OP11_Lwr: DefaultNtCClassColors.OPN,
OP12_Upr: DefaultNtCClassColors.OPN,
OP12_Lwr: DefaultNtCClassColors.OPN,
OP13_Upr: DefaultNtCClassColors.OPN,
OP13_Lwr: DefaultNtCClassColors.OPN,
OP14_Upr: DefaultNtCClassColors.OPN,
OP14_Lwr: DefaultNtCClassColors.OPN,
OP15_Upr: DefaultNtCClassColors.OPN,
OP15_Lwr: DefaultNtCClassColors.OPN,
OP16_Upr: DefaultNtCClassColors.OPN,
OP16_Lwr: DefaultNtCClassColors.OPN,
OP17_Upr: DefaultNtCClassColors.OPN,
OP17_Lwr: DefaultNtCClassColors.OPN,
OP18_Upr: DefaultNtCClassColors.OPN,
OP18_Lwr: DefaultNtCClassColors.OPN,
OP19_Upr: DefaultNtCClassColors.OPN,
OP19_Lwr: DefaultNtCClassColors.OPN,
OP20_Upr: DefaultNtCClassColors.OPN,
OP20_Lwr: DefaultNtCClassColors.OPN,
OP21_Upr: DefaultNtCClassColors.OPN,
OP21_Lwr: DefaultNtCClassColors.OPN,
OP22_Upr: DefaultNtCClassColors.OPN,
OP22_Lwr: DefaultNtCClassColors.OPN,
OP23_Upr: DefaultNtCClassColors.OPN,
OP23_Lwr: DefaultNtCClassColors.OPN,
OP24_Upr: DefaultNtCClassColors.OPN,
OP24_Lwr: DefaultNtCClassColors.OPN,
OP25_Upr: DefaultNtCClassColors.OPN,
OP25_Lwr: DefaultNtCClassColors.OPN,
OP26_Upr: DefaultNtCClassColors.OPN,
OP26_Lwr: DefaultNtCClassColors.OPN,
OP27_Upr: DefaultNtCClassColors.OPN,
OP27_Lwr: DefaultNtCClassColors.OPN,
OP28_Upr: DefaultNtCClassColors.OPN,
OP28_Lwr: DefaultNtCClassColors.OPN,
OP29_Upr: DefaultNtCClassColors.OPN,
OP29_Lwr: DefaultNtCClassColors.OPN,
OP30_Upr: DefaultNtCClassColors.OPN,
OP30_Lwr: DefaultNtCClassColors.OPN,
OP31_Upr: DefaultNtCClassColors.OPN,
OP31_Lwr: DefaultNtCClassColors.OPN,
OPS1_Upr: DefaultNtCClassColors.OPN,
OPS1_Lwr: DefaultNtCClassColors.OPN,
OP1S_Upr: DefaultNtCClassColors.OPN,
OP1S_Lwr: DefaultNtCClassColors.SYN,
AAS1_Upr: DefaultNtCClassColors.SYN,
AAS1_Lwr: DefaultNtCClassColors.A,
AB1S_Upr: DefaultNtCClassColors.A,
AB1S_Lwr: DefaultNtCClassColors.SYN,
AB2S_Upr: DefaultNtCClassColors.A,
AB2S_Lwr: DefaultNtCClassColors.SYN,
BB1S_Upr: DefaultNtCClassColors.B,
BB1S_Lwr: DefaultNtCClassColors.SYN,
BB2S_Upr: DefaultNtCClassColors.B,
BB2S_Lwr: DefaultNtCClassColors.SYN,
BBS1_Upr: DefaultNtCClassColors.SYN,
BBS1_Lwr: DefaultNtCClassColors.B,
ZZ01_Upr: DefaultNtCClassColors.Z,
ZZ01_Lwr: DefaultNtCClassColors.Z,
ZZ02_Upr: DefaultNtCClassColors.Z,
ZZ02_Lwr: DefaultNtCClassColors.Z,
ZZ1S_Upr: DefaultNtCClassColors.Z,
ZZ1S_Lwr: DefaultNtCClassColors.SYN,
ZZ2S_Upr: DefaultNtCClassColors.Z,
ZZ2S_Lwr: DefaultNtCClassColors.SYN,
ZZS1_Upr: DefaultNtCClassColors.SYN,
ZZS1_Lwr: DefaultNtCClassColors.Z,
ZZS2_Upr: DefaultNtCClassColors.SYN,
ZZS2_Lwr: DefaultNtCClassColors.Z,
});

View File

@@ -6,23 +6,22 @@
*/
import { ConfalPyramidsColorThemeProvider } from './color';
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsProvider } from './property';
import { ConfalPyramidsRepresentationProvider } from './representation';
import { ConfalPyramidsTypes } from './types';
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
import { Dnatco } from '../property';
import { DnatcoTypes } from '../types';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider({
export const ConfalPyramidsPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-confal-pyramids',
display: {
name: 'Confal Pyramids', group: 'Annotation',
description: 'Schematic depiction of conformer class and confal value.',
},
isApplicable(a) {
return a.data.models.length >= 1 && a.data.models.some(m => ConfalPyramids.isApplicable(m));
return a.data.models.length >= 1 && a.data.models.some(m => Dnatco.isApplicable(m));
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
@@ -48,50 +47,7 @@ export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider(
}
});
export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean, showToolTip: boolean }>({
name: 'dnatco-confal-pyramids-prop',
category: 'custom-props',
display: {
name: 'Confal Pyramids',
description: 'Schematic depiction of conformer class and confal value.',
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showToolTip: boolean }> {
private provider = ConfalPyramidsProvider;
register(): void {
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
this.ctx.representation.structure.themes.colorThemeRegistry.add(ConfalPyramidsColorThemeProvider);
this.ctx.representation.structure.registry.add(ConfalPyramidsRepresentationProvider);
this.ctx.builders.structure.representation.registerPreset(DnatcoConfalPyramidsPreset);
}
update(p: { autoAttach: boolean, showToolTip: boolean }) {
const updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.params.showToolTip = p.showToolTip;
this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
return updated;
}
unregister() {
this.ctx.customModelProperties.unregister(ConfalPyramidsProvider.descriptor.name);
this.ctx.representation.structure.registry.remove(ConfalPyramidsRepresentationProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(ConfalPyramidsColorThemeProvider);
this.ctx.builders.structure.representation.unregisterPreset(DnatcoConfalPyramidsPreset);
}
},
params: () => ({
autoAttach: PD.Boolean(true),
showToolTip: PD.Boolean(true)
})
});
export function confalPyramidLabel(halfPyramid: ConfalPyramidsTypes.HalfPyramid) {
const { step } = halfPyramid;
export function confalPyramidLabel(step: DnatcoTypes.Step) {
return `
<b>${step.auth_asym_id_1}</b> |
<b>${step.label_comp_id_1} ${step.auth_seq_id_1}${step.PDB_ins_code_1}${step.label_alt_id_1.length > 0 ? ` (alt ${step.label_alt_id_1})` : ''}

View File

@@ -5,8 +5,10 @@
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ErrorColor, NtCColors } from '../color';
import { ConfalPyramidsProvider } from './property';
import { ConfalPyramidsTypes as CPT } from './types';
import { Dnatco } from '../property';
import { Location } from '../../../mol-model/location';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ColorTheme } from '../../../mol-theme/color';
@@ -19,215 +21,7 @@ import { ObjectKeys } from '../../../mol-util/type-helpers';
const Description = 'Assigns colors to confal pyramids';
const DefaultClassColors = {
A: 0xFFC1C1,
B: 0xC8CFFF,
BII: 0x0059DA,
miB: 0x3BE8FB,
Z: 0x01F60E,
IC: 0xFA5CFB,
OPN: 0xE90000,
SYN: 0xFFFF01,
N: 0xF2F2F2,
};
const ErrorColor = Color(0xFFA10A);
const PyramidsColors = ColorMap({
NANT_Upr: DefaultClassColors.N,
NANT_Lwr: DefaultClassColors.N,
AA00_Upr: DefaultClassColors.A,
AA00_Lwr: DefaultClassColors.A,
AA02_Upr: DefaultClassColors.A,
AA02_Lwr: DefaultClassColors.A,
AA03_Upr: DefaultClassColors.A,
AA03_Lwr: DefaultClassColors.A,
AA04_Upr: DefaultClassColors.A,
AA04_Lwr: DefaultClassColors.A,
AA08_Upr: DefaultClassColors.A,
AA08_Lwr: DefaultClassColors.A,
AA09_Upr: DefaultClassColors.A,
AA09_Lwr: DefaultClassColors.A,
AA01_Upr: DefaultClassColors.A,
AA01_Lwr: DefaultClassColors.A,
AA05_Upr: DefaultClassColors.A,
AA05_Lwr: DefaultClassColors.A,
AA06_Upr: DefaultClassColors.A,
AA06_Lwr: DefaultClassColors.A,
AA10_Upr: DefaultClassColors.A,
AA10_Lwr: DefaultClassColors.A,
AA11_Upr: DefaultClassColors.A,
AA11_Lwr: DefaultClassColors.A,
AA07_Upr: DefaultClassColors.A,
AA07_Lwr: DefaultClassColors.A,
AA12_Upr: DefaultClassColors.A,
AA12_Lwr: DefaultClassColors.A,
AA13_Upr: DefaultClassColors.A,
AA13_Lwr: DefaultClassColors.A,
AB01_Upr: DefaultClassColors.A,
AB01_Lwr: DefaultClassColors.B,
AB02_Upr: DefaultClassColors.A,
AB02_Lwr: DefaultClassColors.B,
AB03_Upr: DefaultClassColors.A,
AB03_Lwr: DefaultClassColors.B,
AB04_Upr: DefaultClassColors.A,
AB04_Lwr: DefaultClassColors.B,
AB05_Upr: DefaultClassColors.A,
AB05_Lwr: DefaultClassColors.B,
BA01_Upr: DefaultClassColors.B,
BA01_Lwr: DefaultClassColors.A,
BA05_Upr: DefaultClassColors.B,
BA05_Lwr: DefaultClassColors.A,
BA09_Upr: DefaultClassColors.B,
BA09_Lwr: DefaultClassColors.A,
BA08_Upr: DefaultClassColors.BII,
BA08_Lwr: DefaultClassColors.A,
BA10_Upr: DefaultClassColors.B,
BA10_Lwr: DefaultClassColors.A,
BA13_Upr: DefaultClassColors.BII,
BA13_Lwr: DefaultClassColors.A,
BA16_Upr: DefaultClassColors.BII,
BA16_Lwr: DefaultClassColors.A,
BA17_Upr: DefaultClassColors.BII,
BA17_Lwr: DefaultClassColors.A,
BB00_Upr: DefaultClassColors.B,
BB00_Lwr: DefaultClassColors.B,
BB01_Upr: DefaultClassColors.B,
BB01_Lwr: DefaultClassColors.B,
BB17_Upr: DefaultClassColors.B,
BB17_Lwr: DefaultClassColors.B,
BB02_Upr: DefaultClassColors.B,
BB02_Lwr: DefaultClassColors.B,
BB03_Upr: DefaultClassColors.B,
BB03_Lwr: DefaultClassColors.B,
BB11_Upr: DefaultClassColors.B,
BB11_Lwr: DefaultClassColors.B,
BB16_Upr: DefaultClassColors.B,
BB16_Lwr: DefaultClassColors.B,
BB04_Upr: DefaultClassColors.B,
BB04_Lwr: DefaultClassColors.BII,
BB05_Upr: DefaultClassColors.B,
BB05_Lwr: DefaultClassColors.BII,
BB07_Upr: DefaultClassColors.BII,
BB07_Lwr: DefaultClassColors.BII,
BB08_Upr: DefaultClassColors.BII,
BB08_Lwr: DefaultClassColors.BII,
BB10_Upr: DefaultClassColors.miB,
BB10_Lwr: DefaultClassColors.miB,
BB12_Upr: DefaultClassColors.miB,
BB12_Lwr: DefaultClassColors.miB,
BB13_Upr: DefaultClassColors.miB,
BB13_Lwr: DefaultClassColors.miB,
BB14_Upr: DefaultClassColors.miB,
BB14_Lwr: DefaultClassColors.miB,
BB15_Upr: DefaultClassColors.miB,
BB15_Lwr: DefaultClassColors.miB,
BB20_Upr: DefaultClassColors.miB,
BB20_Lwr: DefaultClassColors.miB,
IC01_Upr: DefaultClassColors.IC,
IC01_Lwr: DefaultClassColors.IC,
IC02_Upr: DefaultClassColors.IC,
IC02_Lwr: DefaultClassColors.IC,
IC03_Upr: DefaultClassColors.IC,
IC03_Lwr: DefaultClassColors.IC,
IC04_Upr: DefaultClassColors.IC,
IC04_Lwr: DefaultClassColors.IC,
IC05_Upr: DefaultClassColors.IC,
IC05_Lwr: DefaultClassColors.IC,
IC06_Upr: DefaultClassColors.IC,
IC06_Lwr: DefaultClassColors.IC,
IC07_Upr: DefaultClassColors.IC,
IC07_Lwr: DefaultClassColors.IC,
OP01_Upr: DefaultClassColors.OPN,
OP01_Lwr: DefaultClassColors.OPN,
OP02_Upr: DefaultClassColors.OPN,
OP02_Lwr: DefaultClassColors.OPN,
OP03_Upr: DefaultClassColors.OPN,
OP03_Lwr: DefaultClassColors.OPN,
OP04_Upr: DefaultClassColors.OPN,
OP04_Lwr: DefaultClassColors.OPN,
OP05_Upr: DefaultClassColors.OPN,
OP05_Lwr: DefaultClassColors.OPN,
OP06_Upr: DefaultClassColors.OPN,
OP06_Lwr: DefaultClassColors.OPN,
OP07_Upr: DefaultClassColors.OPN,
OP07_Lwr: DefaultClassColors.OPN,
OP08_Upr: DefaultClassColors.OPN,
OP08_Lwr: DefaultClassColors.OPN,
OP09_Upr: DefaultClassColors.OPN,
OP09_Lwr: DefaultClassColors.OPN,
OP10_Upr: DefaultClassColors.OPN,
OP10_Lwr: DefaultClassColors.OPN,
OP11_Upr: DefaultClassColors.OPN,
OP11_Lwr: DefaultClassColors.OPN,
OP12_Upr: DefaultClassColors.OPN,
OP12_Lwr: DefaultClassColors.OPN,
OP13_Upr: DefaultClassColors.OPN,
OP13_Lwr: DefaultClassColors.OPN,
OP14_Upr: DefaultClassColors.OPN,
OP14_Lwr: DefaultClassColors.OPN,
OP15_Upr: DefaultClassColors.OPN,
OP15_Lwr: DefaultClassColors.OPN,
OP16_Upr: DefaultClassColors.OPN,
OP16_Lwr: DefaultClassColors.OPN,
OP17_Upr: DefaultClassColors.OPN,
OP17_Lwr: DefaultClassColors.OPN,
OP18_Upr: DefaultClassColors.OPN,
OP18_Lwr: DefaultClassColors.OPN,
OP19_Upr: DefaultClassColors.OPN,
OP19_Lwr: DefaultClassColors.OPN,
OP20_Upr: DefaultClassColors.OPN,
OP20_Lwr: DefaultClassColors.OPN,
OP21_Upr: DefaultClassColors.OPN,
OP21_Lwr: DefaultClassColors.OPN,
OP22_Upr: DefaultClassColors.OPN,
OP22_Lwr: DefaultClassColors.OPN,
OP23_Upr: DefaultClassColors.OPN,
OP23_Lwr: DefaultClassColors.OPN,
OP24_Upr: DefaultClassColors.OPN,
OP24_Lwr: DefaultClassColors.OPN,
OP25_Upr: DefaultClassColors.OPN,
OP25_Lwr: DefaultClassColors.OPN,
OP26_Upr: DefaultClassColors.OPN,
OP26_Lwr: DefaultClassColors.OPN,
OP27_Upr: DefaultClassColors.OPN,
OP27_Lwr: DefaultClassColors.OPN,
OP28_Upr: DefaultClassColors.OPN,
OP28_Lwr: DefaultClassColors.OPN,
OP29_Upr: DefaultClassColors.OPN,
OP29_Lwr: DefaultClassColors.OPN,
OP30_Upr: DefaultClassColors.OPN,
OP30_Lwr: DefaultClassColors.OPN,
OP31_Upr: DefaultClassColors.OPN,
OP31_Lwr: DefaultClassColors.OPN,
OPS1_Upr: DefaultClassColors.OPN,
OPS1_Lwr: DefaultClassColors.OPN,
OP1S_Upr: DefaultClassColors.OPN,
OP1S_Lwr: DefaultClassColors.SYN,
AAS1_Upr: DefaultClassColors.SYN,
AAS1_Lwr: DefaultClassColors.A,
AB1S_Upr: DefaultClassColors.A,
AB1S_Lwr: DefaultClassColors.SYN,
AB2S_Upr: DefaultClassColors.A,
AB2S_Lwr: DefaultClassColors.SYN,
BB1S_Upr: DefaultClassColors.B,
BB1S_Lwr: DefaultClassColors.SYN,
BB2S_Upr: DefaultClassColors.B,
BB2S_Lwr: DefaultClassColors.SYN,
BBS1_Upr: DefaultClassColors.SYN,
BBS1_Lwr: DefaultClassColors.B,
ZZ01_Upr: DefaultClassColors.Z,
ZZ01_Lwr: DefaultClassColors.Z,
ZZ02_Upr: DefaultClassColors.Z,
ZZ02_Lwr: DefaultClassColors.Z,
ZZ1S_Upr: DefaultClassColors.Z,
ZZ1S_Lwr: DefaultClassColors.SYN,
ZZ2S_Upr: DefaultClassColors.Z,
ZZ2S_Lwr: DefaultClassColors.SYN,
ZZS1_Upr: DefaultClassColors.SYN,
ZZS1_Lwr: DefaultClassColors.Z,
ZZS2_Upr: DefaultClassColors.SYN,
ZZS2_Lwr: DefaultClassColors.Z,
});
const PyramidsColors = ColorMap({ ...NtCColors });
type PyramidsColors = typeof PyramidsColors;
export const ConfalPyramidsColorThemeParams = {
@@ -272,7 +66,7 @@ export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramid
factory: ConfalPyramidsColorTheme,
getParams: getConfalPyramidsColorThemeParams,
defaultValues: PD.getDefaultValues(ConfalPyramidsColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => Dnatco.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && ConfalPyramidsProvider.ref(data.structure.models[0], false)

View File

@@ -5,90 +5,18 @@
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramidsTypes as CPT } from './types';
import { Column, Table } from '../../../mol-data/db';
import { toTable } from '../../../mol-io/reader/cif/schema';
import { Dnatco, DnatcoParams, DnatcoSteps } from '../property';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { Model } from '../../../mol-model/structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
export type ConfalPyramids = PropertyWrapper<CPT.Steps | undefined>;
export namespace ConfalPyramids {
export const Schema = {
ndb_struct_ntc_step: {
id: Column.Schema.int,
name: Column.Schema.str,
PDB_model_number: Column.Schema.int,
label_entity_id_1: Column.Schema.int,
label_asym_id_1: Column.Schema.str,
label_seq_id_1: Column.Schema.int,
label_comp_id_1: Column.Schema.str,
label_alt_id_1: Column.Schema.str,
label_entity_id_2: Column.Schema.int,
label_asym_id_2: Column.Schema.str,
label_seq_id_2: Column.Schema.int,
label_comp_id_2: Column.Schema.str,
label_alt_id_2: Column.Schema.str,
auth_asym_id_1: Column.Schema.str,
auth_seq_id_1: Column.Schema.int,
auth_asym_id_2: Column.Schema.str,
auth_seq_id_2: Column.Schema.int,
PDB_ins_code_1: Column.Schema.str,
PDB_ins_code_2: Column.Schema.str,
},
ndb_struct_ntc_step_summary: {
step_id: Column.Schema.int,
assigned_CANA: Column.Schema.str,
assigned_NtC: Column.Schema.str,
confal_score: Column.Schema.int,
euclidean_distance_NtC_ideal: Column.Schema.float,
cartesian_rmsd_closest_NtC_representative: Column.Schema.float,
closest_CANA: Column.Schema.str,
closest_NtC: Column.Schema.str,
closest_step_golden: Column.Schema.str
}
};
export type Schema = typeof Schema;
export async function fromCif(ctx: CustomProperty.Context, model: Model, props: ConfalPyramidsProps): Promise<CustomProperty.Data<ConfalPyramids>> {
const info = PropertyWrapper.createInfo();
const data = getCifData(model);
if (data === undefined) return { value: { info, data: undefined } };
const fromCif = createPyramidsFromCif(model, data.steps, data.stepsSummary);
return { value: { info, data: fromCif } };
}
function getCifData(model: Model) {
if (!MmcifFormat.is(model.sourceData)) throw new Error('Data format must be mmCIF');
if (!hasNdbStructNtcCategories(model)) return undefined;
return {
steps: toTable(Schema.ndb_struct_ntc_step, model.sourceData.data.frame.categories.ndb_struct_ntc_step),
stepsSummary: toTable(Schema.ndb_struct_ntc_step_summary, model.sourceData.data.frame.categories.ndb_struct_ntc_step_summary)
};
}
function hasNdbStructNtcCategories(model: Model): boolean {
if (!MmcifFormat.is(model.sourceData)) return false;
const names = (model.sourceData).data.frame.categoryNames;
return names.includes('ndb_struct_ntc_step') && names.includes('ndb_struct_ntc_step_summary');
}
export function isApplicable(model?: Model): boolean {
return !!model && hasNdbStructNtcCategories(model);
}
}
export const ConfalPyramidsParams = {};
export const ConfalPyramidsParams = { ...DnatcoParams };
export type ConfalPyramidsParams = typeof ConfalPyramidsParams;
export type ConfalPyramidsProps = PD.Values<ConfalPyramidsParams>;
export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramidsParams, ConfalPyramids> = CustomModelProperty.createProvider({
export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramidsParams, DnatcoSteps> = CustomModelProperty.createProvider({
label: 'Confal Pyramids',
descriptor: CustomPropertyDescriptor({
name: 'confal_pyramids',
@@ -96,94 +24,9 @@ export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramids
type: 'static',
defaultParams: ConfalPyramidsParams,
getParams: (data: Model) => ConfalPyramidsParams,
isApplicable: (data: Model) => ConfalPyramids.isApplicable(data),
isApplicable: (data: Model) => Dnatco.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ConfalPyramidsProps>) => {
const p = { ...PD.getDefaultValues(ConfalPyramidsParams), ...props };
return ConfalPyramids.fromCif(ctx, data, p);
return Dnatco.fromCif(ctx, data, p);
}
});
type StepsSummaryTable = Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step_summary>;
function createPyramidsFromCif(
model: Model,
cifSteps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>,
stepsSummary: StepsSummaryTable
): CPT.Steps {
const steps = new Array<CPT.Step>();
const mapping = new Array<CPT.MappedChains>();
const {
id, PDB_model_number, name,
auth_asym_id_1, auth_seq_id_1, label_comp_id_1, label_alt_id_1, PDB_ins_code_1,
auth_asym_id_2, auth_seq_id_2, label_comp_id_2, label_alt_id_2, PDB_ins_code_2,
_rowCount
} = cifSteps;
if (_rowCount !== stepsSummary._rowCount) throw new Error('Inconsistent mmCIF data');
for (let i = 0; i < _rowCount; i++) {
const {
NtC,
confal_score,
rmsd
} = getSummaryData(id.value(i), i, stepsSummary);
const modelNum = PDB_model_number.value(i);
const chainId = auth_asym_id_1.value(i);
const seqId = auth_seq_id_1.value(i);
const modelIdx = modelNum - 1;
if (mapping.length <= modelIdx || !mapping[modelIdx])
mapping[modelIdx] = new Map<string, CPT.MappedResidues>();
const step = {
PDB_model_number: modelNum,
name: name.value(i),
auth_asym_id_1: chainId,
auth_seq_id_1: seqId,
label_comp_id_1: label_comp_id_1.value(i),
label_alt_id_1: label_alt_id_1.value(i),
PDB_ins_code_1: PDB_ins_code_1.value(i),
auth_asym_id_2: auth_asym_id_2.value(i),
auth_seq_id_2: auth_seq_id_2.value(i),
label_comp_id_2: label_comp_id_2.value(i),
label_alt_id_2: label_alt_id_2.value(i),
PDB_ins_code_2: PDB_ins_code_2.value(i),
confal_score,
NtC,
rmsd,
};
steps.push(step);
const mappedChains = mapping[modelIdx];
const residuesOnChain = mappedChains.get(chainId) ?? new Map<number, number[]>();
const stepsForResidue = residuesOnChain.get(seqId) ?? [];
stepsForResidue.push(steps.length - 1);
residuesOnChain.set(seqId, stepsForResidue);
mappedChains.set(chainId, residuesOnChain);
mapping[modelIdx] = mappedChains;
}
return { steps, mapping };
}
function getSummaryData(id: number, i: number, stepsSummary: StepsSummaryTable) {
const {
step_id,
confal_score,
assigned_NtC,
cartesian_rmsd_closest_NtC_representative,
} = stepsSummary;
// Assume that step_ids in ntc_step_summary are in the same order as steps in ntc_step
for (let j = i; j < stepsSummary._rowCount; j++) {
if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) };
}
// Safety net for cases where the previous assumption is not met
for (let j = 0; j < i; j++) {
if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) };
}
throw new Error('Inconsistent mmCIF data');
}

View File

@@ -5,9 +5,10 @@
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsProvider } from './property';
import { ConfalPyramidsIterator } from './util';
import { ConfalPyramidsTypes as CPT } from './types';
import { Dnatco } from '../property';
import { Interval } from '../../../mol-data/int';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
@@ -87,6 +88,8 @@ function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Str
const it = new ConfalPyramidsIterator(structure, unit);
while (it.hasNext) {
const allPoints = it.move();
if (!allPoints)
continue;
for (const points of allPoints) {
const { O3, P, OP1, OP2, O5, confalScore } = points;
@@ -150,9 +153,7 @@ function getConfalPyramidLoci(pickingId: PickingId, structureGroup: StructureGro
if (halfPyramidsCount <= groupId) return EmptyLoci;
const idx = Math.floor(groupId / 2); // Map groupIndex to a step, see createConfalPyramidsMesh() for full explanation
const step = data.steps[idx];
return CPT.Loci({ step, isLower: groupId % 2 === 1 }, [{}]);
return CPT.Loci(data.steps, [idx]);
}
function eachConfalPyramid(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
@@ -197,7 +198,7 @@ export const ConfalPyramidsRepresentationProvider = StructureRepresentationProvi
defaultValues: PD.getDefaultValues(ConfalPyramidsParams),
defaultColorTheme: { name: 'confal-pyramids' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.models.some(m => ConfalPyramids.isApplicable(m)),
isApplicable: (structure: Structure) => structure.models.some(m => Dnatco.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => ConfalPyramidsProvider.attach(ctx, structure.model, void 0, true),
detach: (data) => ConfalPyramidsProvider.ref(data.model, false),

View File

@@ -5,61 +5,29 @@
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { DnatcoTypes } from '../types';
import { DataLocation } from '../../../mol-model/location';
import { DataLoci } from '../../../mol-model/loci';
import { confalPyramidLabel } from './behavior';
export namespace ConfalPyramidsTypes {
export const DataTag = 'dnatco-confal-half-pyramid';
export interface Location extends DataLocation<DnatcoTypes.HalfStep, {}> {}
export type Step = {
PDB_model_number: number,
name: string,
auth_asym_id_1: string,
auth_seq_id_1: number,
label_comp_id_1: string,
label_alt_id_1: string,
PDB_ins_code_1: string,
auth_asym_id_2: string,
auth_seq_id_2: number,
label_comp_id_2: string,
label_alt_id_2: string,
PDB_ins_code_2: string,
confal_score: number,
NtC: string,
rmsd: number,
}
export type MappedChains = Map<string, MappedResidues>;
export type MappedResidues = Map<number, number[]>;
export interface Steps {
steps: Array<Step>,
mapping: MappedChains[],
}
export interface HalfPyramid {
step: Step,
isLower: boolean,
}
export interface Location extends DataLocation<HalfPyramid, {}> {}
export function Location(step: Step, isLower: boolean) {
return DataLocation(DataTag, { step, isLower }, {});
export function Location(step: DnatcoTypes.Step, isLower: boolean) {
return DataLocation(DnatcoTypes.DataTag, { step, isLower }, {});
}
export function isLocation(x: any): x is Location {
return !!x && x.kind === 'data-location' && x.tag === DataTag;
return !!x && x.kind === 'data-location' && x.tag === DnatcoTypes.DataTag;
}
export interface Loci extends DataLoci<HalfPyramid, {}> {}
export interface Loci extends DataLoci<DnatcoTypes.Step[], number> {}
export function Loci(data: HalfPyramid, elements: ReadonlyArray<{}>): Loci {
return DataLoci(DataTag, data, elements, undefined, () => confalPyramidLabel(data));
export function Loci(data: DnatcoTypes.Step[], elements: ReadonlyArray<number>): Loci {
return DataLoci(DnatcoTypes.DataTag, data, elements, undefined, () => elements[0] !== undefined ? confalPyramidLabel(data[elements[0]]) : '');
}
export function isLoci(x: any): x is Loci {
return !!x && x.kind === 'data-loci' && x.tag === DataTag;
return !!x && x.kind === 'data-loci' && x.tag === DnatcoTypes.DataTag;
}
}

View File

@@ -1,16 +1,15 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramidsProvider } from './property';
import { ConfalPyramidsTypes as CPT } from './types';
import { DnatcoTypes } from '../types';
import { DnatcoUtil } from '../util';
import { Segmentation } from '../../../mol-data/int';
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
type Residue = Segmentation.Segment<ResidueIndex>;
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure';
export type Pyramid = {
O3: ElementIndex,
@@ -22,31 +21,17 @@ export type Pyramid = {
stepIdx: number,
};
const EmptyStepIndices = new Array<number>();
function copyResidue(r?: Residue) {
return r ? { index: r.index, start: r.start, end: r.end } : void 0;
}
function getAtomIndex(loc: StructureElement.Location, residue: Residue, names: string[], altId: string): ElementIndex {
for (let eI = residue.start; eI < residue.end; eI++) {
loc.element = loc.unit.elements[eI];
const elName = StructureProperties.atom.label_atom_id(loc);
const elAltId = StructureProperties.atom.label_alt_id(loc);
if (names.includes(elName) && (elAltId === altId || elAltId.length === 0))
return loc.element;
}
return -1 as ElementIndex;
}
function getPyramid(loc: StructureElement.Location, one: Residue, two: Residue, altIdOne: string, altIdTwo: string, confalScore: number, stepIdx: number): Pyramid {
const O3 = getAtomIndex(loc, one, ['O3\'', 'O3*'], altIdOne);
const P = getAtomIndex(loc, two, ['P'], altIdTwo);
const OP1 = getAtomIndex(loc, two, ['OP1'], altIdTwo);
const OP2 = getAtomIndex(loc, two, ['OP2'], altIdTwo);
const O5 = getAtomIndex(loc, two, ['O5\'', 'O5*'], altIdTwo);
function getPyramid(
loc: StructureElement.Location,
one: DnatcoUtil.Residue, two: DnatcoUtil.Residue,
altIdOne: string, altIdTwo: string,
insCodeOne: string, insCodeTwo: string,
confalScore: number, stepIdx: number): Pyramid {
const O3 = DnatcoUtil.getAtomIndex(loc, one, ['O3\'', 'O3*'], altIdOne, insCodeOne);
const P = DnatcoUtil.getAtomIndex(loc, two, ['P'], altIdTwo, insCodeTwo);
const OP1 = DnatcoUtil.getAtomIndex(loc, two, ['OP1'], altIdTwo, insCodeTwo);
const OP2 = DnatcoUtil.getAtomIndex(loc, two, ['OP2'], altIdTwo, insCodeTwo);
const O5 = DnatcoUtil.getAtomIndex(loc, two, ['O5\'', 'O5*'], altIdTwo, insCodeTwo);
return { O3, P, OP1, OP2, O5, confalScore, stepIdx };
}
@@ -54,39 +39,29 @@ function getPyramid(loc: StructureElement.Location, one: Residue, two: Residue,
export class ConfalPyramidsIterator {
private chainIt: Segmentation.SegmentIterator<ChainIndex>;
private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
private residueOne?: Residue;
private residueTwo: Residue;
private data?: CPT.Steps;
private residueOne?: DnatcoUtil.Residue;
private residueTwo: DnatcoUtil.Residue;
private data?: DnatcoTypes.Steps;
private loc: StructureElement.Location;
private getStepIndices(r: Residue) {
this.loc.element = this.loc.unit.elements[r.start];
const modelIdx = StructureProperties.unit.model_num(this.loc) - 1;
const chainId = StructureProperties.chain.auth_asym_id(this.loc);
const seqId = StructureProperties.residue.auth_seq_id(this.loc);
const chains = this.data!.mapping[modelIdx];
if (!chains) return EmptyStepIndices;
const residues = chains.get(chainId);
if (!residues) return EmptyStepIndices;
return residues.get(seqId) ?? EmptyStepIndices;
}
private moveStep() {
this.residueOne = copyResidue(this.residueTwo);
this.residueTwo = copyResidue(this.residueIt.move())!;
this.residueOne = DnatcoUtil.copyResidue(this.residueTwo);
this.residueTwo = DnatcoUtil.copyResidue(this.residueIt.move())!;
// Check for discontinuity
if (this.residueTwo.index !== (this.residueOne!.index + 1))
return void 0;
return this.toPyramids(this.residueOne!, this.residueTwo);
}
private toPyramids(one: Residue, two: Residue) {
const indices = this.getStepIndices(one);
private toPyramids(one: DnatcoUtil.Residue, two: DnatcoUtil.Residue) {
const indices = DnatcoUtil.getStepIndices(this.data!, this.loc, one);
const points = [];
for (const idx of indices) {
const step = this.data!.steps[idx];
points.push(getPyramid(this.loc, one, two, step.label_alt_id_1, step.label_alt_id_2, step.confal_score, idx));
points.push(getPyramid(this.loc, one, two, step.label_alt_id_1, step.label_alt_id_2, step.PDB_ins_code_1, step.PDB_ins_code_2, step.confal_score, idx));
}
return points;
@@ -121,6 +96,8 @@ export class ConfalPyramidsIterator {
return this.moveStep();
} else {
this.residueIt.setSegment(this.chainIt.move());
if (this.residueIt.hasNext)
this.residueTwo = this.residueIt.move();
return this.moveStep();
}
}

View File

@@ -1,8 +1,8 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
export { DnatcoConfalPyramids } from './confal-pyramids/behavior';
export { DnatcoNtCs } from './behavior';

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { NtCTubeColorThemeProvider } from './color';
import { NtCTubeProvider } from './property';
import { NtCTubeRepresentationProvider } from './representation';
import { DnatcoTypes } from '../types';
import { Dnatco } from '../property';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../../mol-state';
import { Task } from '../../../mol-task';
export const NtCTubePreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-ntc-tube',
display: {
name: 'NtC Tube', group: 'Annotation',
description: 'NtC Tube',
},
isApplicable(a) {
return a.data.models.length >= 1 && a.data.models.some(m => Dnatco.isApplicable(m));
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const model = structureCell?.obj?.data.model;
if (!structureCell || !model) return {};
await plugin.runTask(Task.create('NtC tube', async runtime => {
await NtCTubeProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
}));
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params }, plugin);
const tube = await plugin.builders.structure.tryCreateComponentStatic(structureCell, 'nucleic', { label: 'NtC Tube' });
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
let tubeRepr;
if (representations)
tubeRepr = builder.buildRepresentation(update, tube, { type: NtCTubeRepresentationProvider, typeParams, color: NtCTubeColorThemeProvider }, { tag: 'ntc-tube' });
await update.commit({ revertOnError: true });
return { components: { ...components, tube }, representations: { ...representations, tubeRepr } };
}
});
export function NtCTubeSegmentLabel(step: DnatcoTypes.Step) {
return `
<b>${step.auth_asym_id_1}</b> |
<b>${step.label_comp_id_1} ${step.auth_seq_id_1}${step.PDB_ins_code_1}${step.label_alt_id_1.length > 0 ? ` (alt ${step.label_alt_id_1})` : ''}
${step.label_comp_id_2} ${step.auth_seq_id_2}${step.PDB_ins_code_2}${step.label_alt_id_2.length > 0 ? ` (alt ${step.label_alt_id_2})` : ''} </b><br />
<i>NtC:</i> ${step.NtC} | <i>Confal score:</i> ${step.confal_score} | <i>RMSD:</i> ${step.rmsd.toFixed(2)}
`;
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ErrorColor, NtCColors } from '../color';
import { NtCTubeProvider } from './property';
import { NtCTubeTypes as NTT } from './types';
import { Dnatco } from '../property';
import { Location } from '../../../mol-model/location';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ColorTheme } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color, ColorMap } from '../../../mol-util/color';
import { getColorMapParams } from '../../../mol-util/color/params';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { TableLegend } from '../../../mol-util/legend';
import { ObjectKeys } from '../../../mol-util/type-helpers';
const Description = 'Assigns colors to NtC Tube segments';
const NtCTubeColors = ColorMap({
...NtCColors,
residueMarker: Color(0x222222),
stepBoundaryMarker: Color(0x656565),
});
type NtCTubeColors = typeof NtCTubeColors;
export const NtCTubeColorThemeParams = {
colors: PD.MappedStatic('default', {
'default': PD.EmptyGroup(),
'custom': PD.Group(getColorMapParams(NtCTubeColors))
}),
markResidueBoundaries: PD.Boolean(true),
markSegmentBoundaries: PD.Boolean(true),
};
export type NtCTubeColorThemeParams = typeof NtCTubeColorThemeParams;
export function getNtCTubeColorThemeParams(ctx: ThemeDataContext) {
return PD.clone(NtCTubeColorThemeParams);
}
export function NtCTubeColorTheme(ctx: ThemeDataContext, props: PD.Values<NtCTubeColorThemeParams>): ColorTheme<NtCTubeColorThemeParams> {
const colorMap = props.colors.name === 'default' ? NtCTubeColors : props.colors.params;
function color(location: Location, isSecondary: boolean): Color {
if (NTT.isLocation(location)) {
const { data } = location;
const { step, kind } = data;
let key;
if (kind === 'upper')
key = step.NtC + '_Upr' as keyof NtCTubeColors;
else if (kind === 'lower')
key = step.NtC + '_Lwr' as keyof NtCTubeColors;
else if (kind === 'residue-boundary')
key = (!props.markResidueBoundaries ? step.NtC + '_Lwr' : 'residueMarker') as keyof NtCTubeColors;
else /* segment-boundary */
key = (!props.markSegmentBoundaries ? step.NtC + '_Lwr' : 'stepBoundaryMarker') as keyof NtCTubeColors;
return colorMap[key] ?? ErrorColor;
}
return ErrorColor;
}
return {
factory: NtCTubeColorTheme,
granularity: 'group',
color,
props,
description: Description,
legend: TableLegend(ObjectKeys(colorMap).map(k => [k.replace('_', ' '), colorMap[k]] as [string, Color]).concat([['Error', ErrorColor]])),
};
}
export const NtCTubeColorThemeProvider: ColorTheme.Provider<NtCTubeColorThemeParams, 'ntc-tube'> = {
name: 'ntc-tube',
label: 'NtC Tube',
category: ColorTheme.Category.Residue,
factory: NtCTubeColorTheme,
getParams: getNtCTubeColorThemeParams,
defaultValues: PD.getDefaultValues(NtCTubeColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => Dnatco.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? NtCTubeProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && NtCTubeProvider.ref(data.structure.models[0], false)
}
};

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { NtCTubeTypes as NTT } from './types';
import { Dnatco, DnatcoParams } from '../property';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { Model } from '../../../mol-model/structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export const NtCTubeParams = { ...DnatcoParams };
export type NtCTubeParams = typeof NtCTubeParams;
export type NtCTubeProps = PD.Values<NtCTubeParams>;
export type NtCTubeData = PropertyWrapper<NTT.Data | undefined>;
async function fromCif(ctx: CustomProperty.Context, model: Model, props: NtCTubeProps): Promise<CustomProperty.Data<NtCTubeData>> {
const info = PropertyWrapper.createInfo();
const data = Dnatco.getCifData(model);
if (data === undefined) return { value: { info, data: undefined } };
const steps = Dnatco.getStepsFromCif(model, data.steps, data.stepsSummary);
return { value: { info, data: { data: steps } } };
}
export const NtCTubeProvider: CustomModelProperty.Provider<NtCTubeParams, NtCTubeData> = CustomModelProperty.createProvider({
label: 'NtC Tube',
descriptor: CustomPropertyDescriptor({
name: 'ntc-tube',
}),
type: 'static',
defaultParams: NtCTubeParams,
getParams: (data: Model) => NtCTubeParams,
isApplicable: (data: Model) => Dnatco.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<NtCTubeProps>) => {
const p = { ...PD.getDefaultValues(NtCTubeParams), ...props };
return fromCif(ctx, data, p);
}
});

View File

@@ -0,0 +1,454 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { NtCTubeProvider } from './property';
import { NtCTubeSegmentsIterator } from './util';
import { NtCTubeTypes as NTT } from './types';
import { Dnatco } from '../property';
import { DnatcoTypes } from '../types';
import { DnatcoUtil } from '../util';
import { Interval } from '../../../mol-data/int';
import { BaseGeometry, VisualQuality } from '../../../mol-geo/geometry/base';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { addFixedCountDashedCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { addTube } from '../../../mol-geo/geometry/mesh/builder/tube';
import { PickingId } from '../../../mol-geo/geometry/picking';
import { CylinderProps } from '../../../mol-geo/primitive/cylinder';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { Sphere3D } from '../../../mol-math/geometry/primitives/sphere3d';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { smoothstep } from '../../../mol-math/interpolate';
import { NullLocation } from '../../../mol-model/location';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Structure, StructureElement, Unit } from '../../../mol-model/structure';
import { structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { createCurveSegmentState, CurveSegmentState } from '../../../mol-repr/structure/visual/util/polymer';
import { getStructureQuality, VisualUpdateState } from '../../../mol-repr/util';
import { VisualContext } from '../../../mol-repr/visual';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
const v3add = Vec3.add;
const v3copy = Vec3.copy;
const v3cross = Vec3.cross;
const v3fromArray = Vec3.fromArray;
const v3matchDirection = Vec3.matchDirection;
const v3normalize = Vec3.normalize;
const v3orthogonalize = Vec3.orthogonalize;
const v3scale = Vec3.scale;
const v3slerp = Vec3.slerp;
const v3spline = Vec3.spline;
const v3sub = Vec3.sub;
const v3toArray = Vec3.toArray;
const NtCTubeMeshParams = {
...UnitsMeshParams,
linearSegments: PD.Numeric(4, { min: 2, max: 8, step: 1 }, BaseGeometry.CustomQualityParamInfo),
radialSegments: PD.Numeric(22, { min: 4, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
residueMarkerWidth: PD.Numeric(0.05, { min: 0.01, max: 0.25, step: 0.01 }),
segmentBoundaryWidth: PD.Numeric(0.05, { min: 0.01, max: 0.25, step: 0.01 }),
};
type NtCTubeMeshParams = typeof NtCTubeMeshParams;
type QualityOptions = Exclude<VisualQuality, 'auto' | 'custom'>;
const LinearSegmentCount: Record<QualityOptions, number> = {
highest: 6,
higher: 6,
high: 4,
medium: 4,
low: 3,
lower: 3,
lowest: 2,
};
const RadialSegmentCount: Record<QualityOptions, number> = {
highest: 32,
higher: 26,
high: 22,
medium: 18,
low: 14,
lower: 10,
lowest: 6,
};
const _curvePoint = Vec3();
const _tanA = Vec3();
const _tanB = Vec3();
const _firstTangentVec = Vec3();
const _lastTangentVec = Vec3();
const _firstNormalVec = Vec3();
const _lastNormalVec = Vec3();
const _tmpNormal = Vec3();
const _tangentVec = Vec3();
const _normalVec = Vec3();
const _binormalVec = Vec3();
const _prevNormal = Vec3();
const _nextNormal = Vec3();
function interpolatePointsAndTangents(state: CurveSegmentState, p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, tRange: number[]) {
const { curvePoints, tangentVectors, linearSegments } = state;
const tension = 0.5;
const r = tRange[1] - tRange[0];
for (let j = 0; j <= linearSegments; ++j) {
const t = j * r / linearSegments + tRange[0];
v3spline(_curvePoint, p0, p1, p2, p3, t, tension);
v3spline(_tanA, p0, p1, p2, p3, t - 0.01, tension);
v3spline(_tanB, p0, p1, p2, p3, t + 0.01, tension);
v3toArray(_curvePoint, curvePoints, j * 3);
v3normalize(_tangentVec, v3sub(_tangentVec, _tanA, _tanB));
v3toArray(_tangentVec, tangentVectors, j * 3);
}
}
function interpolateNormals(state: CurveSegmentState, firstDirection: Vec3, lastDirection: Vec3) {
const { curvePoints, tangentVectors, normalVectors, binormalVectors } = state;
const n = curvePoints.length / 3;
v3fromArray(_firstTangentVec, tangentVectors, 0);
v3fromArray(_lastTangentVec, tangentVectors, (n - 1) * 3);
v3orthogonalize(_firstNormalVec, _firstTangentVec, firstDirection);
v3orthogonalize(_lastNormalVec, _lastTangentVec, lastDirection);
v3matchDirection(_lastNormalVec, _lastNormalVec, _firstNormalVec);
v3copy(_prevNormal, _firstNormalVec);
const n1 = n - 1;
for (let i = 0; i < n; ++i) {
const j = smoothstep(0, n1, i) * n1;
const t = i === 0 ? 0 : 1 / (n - j);
v3fromArray(_tangentVec, tangentVectors, i * 3);
v3orthogonalize(_normalVec, _tangentVec, v3slerp(_tmpNormal, _prevNormal, _lastNormalVec, t));
v3toArray(_normalVec, normalVectors, i * 3);
v3copy(_prevNormal, _normalVec);
v3normalize(_binormalVec, v3cross(_binormalVec, _tangentVec, _normalVec));
v3toArray(_binormalVec, binormalVectors, i * 3);
}
for (let i = 1; i < n1; ++i) {
v3fromArray(_prevNormal, normalVectors, (i - 1) * 3);
v3fromArray(_normalVec, normalVectors, i * 3);
v3fromArray(_nextNormal, normalVectors, (i + 1) * 3);
v3scale(_normalVec, v3add(_normalVec, _prevNormal, v3add(_normalVec, _nextNormal, _normalVec)), 1 / 3);
v3toArray(_normalVec, normalVectors, i * 3);
v3fromArray(_tangentVec, tangentVectors, i * 3);
v3normalize(_binormalVec, v3cross(_binormalVec, _tangentVec, _normalVec));
v3toArray(_binormalVec, binormalVectors, i * 3);
}
}
function interpolate(state: CurveSegmentState, p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, firstDir: Vec3, lastDir: Vec3, tRange = [0, 1]) {
interpolatePointsAndTangents(state, p0, p1, p2, p3, tRange);
interpolateNormals(state, firstDir, lastDir);
}
function createNtCTubeSegmentsIterator(structureGroup: StructureGroup): LocationIterator {
const { structure, group } = structureGroup;
const instanceCount = group.units.length;
const data = NtCTubeProvider.get(structure.model)?.value?.data;
if (!data) return LocationIterator(0, 1, 1, () => NullLocation);
const numBlocks = data.data.steps.length * 4;
const getLocation = (groupId: number, instanceId: number) => {
if (groupId > numBlocks) return NullLocation;
const stepIdx = Math.floor(groupId / 4);
const step = data.data.steps[stepIdx];
const r = groupId % 4;
const kind =
r === 0 ? 'upper' :
r === 1 ? 'lower' :
r === 2 ? 'residue-boundary' : 'segment-boundary';
return NTT.Location({ step, kind });
};
return LocationIterator(totalMeshGroupsCount(data.data.steps) + 1, instanceCount, 1, getLocation);
}
function segmentCount(structure: Structure, props: PD.Values<NtCTubeMeshParams>): { linear: number, radial: number } {
const quality = props.quality;
if (quality === 'custom')
return { linear: props.linearSegments, radial: props.radialSegments };
else if (quality === 'auto') {
const autoQuality = getStructureQuality(structure) as QualityOptions;
return { linear: LinearSegmentCount[autoQuality], radial: RadialSegmentCount[autoQuality] };
} else
return { linear: LinearSegmentCount[quality], radial: RadialSegmentCount[quality] };
}
function stepBoundingSphere(step: DnatcoTypes.Step, struLoci: StructureElement.Loci): Sphere3D | undefined {
const one = DnatcoUtil.residueToLoci(step.auth_asym_id_1, step.auth_seq_id_1, step.label_alt_id_1, step.PDB_ins_code_1, struLoci, 'auth');
const two = DnatcoUtil.residueToLoci(step.auth_asym_id_2, step.auth_seq_id_2, step.label_alt_id_2, step.PDB_ins_code_2, struLoci, 'auth');
if (StructureElement.Loci.is(one) && StructureElement.Loci.is(two)) {
const union = structureUnion(struLoci.structure, [StructureElement.Loci.toStructure(one), StructureElement.Loci.toStructure(two)]);
return union.boundary.sphere;
}
return void 0;
}
function totalMeshGroupsCount(steps: DnatcoTypes.Step[]) {
// Each segment has two blocks, Residue Boundary marker and a Segment Boundary marker
return steps.length * 4 - 1; // Subtract one because the last Segment Boundary marker is not drawn
}
function createNtCTubeMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<NtCTubeMeshParams>, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
const prop = NtCTubeProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) return Mesh.createEmpty(mesh);
const { data } = prop.data;
if (data.steps.length === 0) return Mesh.createEmpty(mesh);
const MarkerLinearSegmentCount = 2;
const segCount = segmentCount(structure, props);
const vertexCount = Math.floor((segCount.linear * 4 * data.steps.length / structure.model.atomicHierarchy.chains._rowCount) * segCount.radial);
const chunkSize = Math.floor(vertexCount / 3);
const diameter = 1.0 * theme.size.props.value;
const mb = MeshBuilder.createState(vertexCount, chunkSize, mesh);
const state = createCurveSegmentState(segCount.linear);
const { curvePoints, normalVectors, binormalVectors, widthValues, heightValues } = state;
for (let idx = 0; idx <= segCount.linear; idx++) {
widthValues[idx] = diameter;
heightValues[idx] = diameter;
}
const [normals, binormals] = [binormalVectors, normalVectors]; // Needed so that the tube is not drawn from inside out
const markerState = createCurveSegmentState(MarkerLinearSegmentCount);
const { curvePoints: mCurvePoints, normalVectors: mNormalVectors, binormalVectors: mBinormalVectors, widthValues: mWidthValues, heightValues: mHeightValues } = markerState;
for (let idx = 0; idx <= MarkerLinearSegmentCount; idx++) {
mWidthValues[idx] = diameter;
mHeightValues[idx] = diameter;
}
const [mNormals, mBinormals] = [mBinormalVectors, mNormalVectors];
const firstDir = Vec3();
const lastDir = Vec3();
const markerDir = Vec3();
const residueMarkerWidth = props.residueMarkerWidth / 2;
const it = new NtCTubeSegmentsIterator(structure, unit);
while (it.hasNext) {
const segment = it.move();
if (!segment)
continue;
const { p_1, p0, p1, p2, p3, p4, pP } = segment;
const FirstBlockId = segment.stepIdx * 4;
const SecondBlockId = FirstBlockId + 1;
const ResidueMarkerId = FirstBlockId + 2;
const SegmentBoundaryMarkerId = FirstBlockId + 3;
const { rmShift, rmPos } = calcResidueMarkerShift(p2, p3, pP);
if (segment.firstInChain) {
v3normalize(firstDir, v3sub(firstDir, p2, p1));
v3normalize(lastDir, v3sub(lastDir, rmPos, p2));
} else {
v3copy(firstDir, lastDir);
v3normalize(lastDir, v3sub(lastDir, rmPos, p2));
}
// C5' -> O3' block
interpolate(state, p0, p1, p2, p3, firstDir, lastDir);
mb.currentGroup = FirstBlockId;
addTube(mb, curvePoints, normals, binormals, segCount.linear, segCount.radial, widthValues, heightValues, segment.firstInChain || segment.followsGap, false, 'rounded');
// O3' -> C5' block
v3copy(firstDir, lastDir);
v3normalize(markerDir, v3sub(markerDir, p3, rmPos));
v3normalize(lastDir, v3sub(lastDir, p4, p3));
// From O3' to the residue marker
interpolate(state, p1, p2, p3, p4, firstDir, markerDir, [0, rmShift - residueMarkerWidth]);
mb.currentGroup = SecondBlockId;
addTube(mb, curvePoints, normals, binormals, segCount.linear, segCount.radial, widthValues, heightValues, false, false, 'rounded');
// Residue marker
interpolate(markerState, p1, p2, p3, p4, markerDir, markerDir, [rmShift - residueMarkerWidth, rmShift + residueMarkerWidth]);
mb.currentGroup = ResidueMarkerId;
addTube(mb, mCurvePoints, mNormals, mBinormals, MarkerLinearSegmentCount, segCount.radial, mWidthValues, mHeightValues, false, false, 'rounded');
if (segment.capEnd) {
// From the residue marker to C5' of the end
interpolate(state, p1, p2, p3, p4, markerDir, lastDir, [rmShift + residueMarkerWidth, 1]);
mb.currentGroup = SecondBlockId;
addTube(mb, curvePoints, normals, binormals, segCount.linear, segCount.radial, widthValues, heightValues, false, true, 'rounded');
} else {
// From the residue marker to C5' of the step boundary marker
interpolate(state, p1, p2, p3, p4, markerDir, lastDir, [rmShift + residueMarkerWidth, 1 - props.segmentBoundaryWidth]);
mb.currentGroup = SecondBlockId;
addTube(mb, curvePoints, normals, binormals, segCount.linear, segCount.radial, widthValues, heightValues, false, false, 'rounded');
// Step boundary marker
interpolate(markerState, p1, p2, p3, p4, lastDir, lastDir, [1 - props.segmentBoundaryWidth, 1]);
mb.currentGroup = SegmentBoundaryMarkerId;
addTube(mb, mCurvePoints, mNormals, mBinormals, MarkerLinearSegmentCount, segCount.radial, mWidthValues, mHeightValues, false, false, 'rounded');
}
if (segment.followsGap) {
const cylinderProps: CylinderProps = {
radiusTop: diameter / 2, radiusBottom: diameter / 2, topCap: true, bottomCap: true, radialSegments: segCount.radial,
};
mb.currentGroup = FirstBlockId;
addFixedCountDashedCylinder(mb, p_1, p1, 1, 2 * segCount.linear, cylinderProps);
}
}
const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1.05);
const m = MeshBuilder.getMesh(mb);
m.setBoundingSphere(boundingSphere);
return m;
}
const _rmvCO = Vec3();
const _rmvPO = Vec3();
const _rmPos = Vec3();
const _HalfPi = Math.PI / 2;
function calcResidueMarkerShift(pO: Vec3, pC: Vec3, pP: Vec3): { rmShift: number, rmPos: Vec3 } {
v3sub(_rmvCO, pC, pO);
v3sub(_rmvPO, pP, pO);
// Project position of P atom on the O3' -> C5' vector
const beta = Vec3.angle(_rmvPO, _rmvCO);
const alpha = _HalfPi - Math.abs(beta);
const lengthMO = Math.cos(alpha) * Vec3.magnitude(_rmvPO);
const shift = lengthMO / Vec3.magnitude(_rmvCO);
v3scale(_rmvCO, _rmvCO, shift);
v3add(_rmPos, _rmvCO, pO);
return { rmShift: shift, rmPos: _rmPos };
}
function getNtCTubeSegmentLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
const { groupId, objectId, instanceId } = pickingId;
if (objectId !== id) return EmptyLoci;
const { structure } = structureGroup;
const unit = structureGroup.group.units[instanceId];
if (!Unit.isAtomic(unit)) return EmptyLoci;
const data = NtCTubeProvider.get(structure.model)?.value?.data ?? undefined;
if (!data) return EmptyLoci;
const MeshGroupsCount = totalMeshGroupsCount(data.data.steps);
if (groupId > MeshGroupsCount) return EmptyLoci;
const stepIdx = Math.floor(groupId / 4);
const bs = stepBoundingSphere(data.data.steps[stepIdx], Structure.toStructureElementLoci(structure));
/*
* NOTE 1) Each step is drawn with 4 mesh groups. We need to divide/multiply by 4 to convert between steps and mesh groups.
* NOTE 2) Molstar will create a mesh only for the asymmetric unit. When the entire biological assembly
* is displayed, Molstar just copies and transforms the mesh. This means that even though the mesh
* might be displayed multiple times, groupIds of the individual blocks in the mesh will be the same.
* If there are multiple copies of a mesh, Molstar needs to be able to tell which block belongs to which copy of the mesh.
* To do that, Molstar adds an offset to groupIds of the copied meshes. Offset is calculated as follows:
*
* offset = NumberOfBlocks * UnitIndex
*
* "NumberOfBlocks" is the number of valid Location objects got from LocationIterator *or* the greatest groupId set by
* the mesh generator - whichever is smaller.
*
* UnitIndex is the index of the Unit the mesh belongs to, starting from 0. (See "unitMap" in the Structure object).
* We can also get this index from the value "instanceId" of the "pickingId" object.
*
* If this offset is not applied, picking a piece of one of the copied meshes would actually pick that piece in the original mesh.
* This is particularly apparent with highlighting - hovering over items in a copied mesh incorrectly highlights those items in the source mesh.
*
* Molstar can take advantage of the fact that ElementLoci has a reference to the Unit object attached to it. Since we cannot attach ElementLoci
* to a step, we need to calculate the offseted groupId here and pass it as part of the DataLoci.
*/
const offsetGroupId = stepIdx * 4 + (MeshGroupsCount + 1) * instanceId;
return NTT.Loci(data.data.steps, [stepIdx], [offsetGroupId], bs);
}
function eachNtCTubeSegment(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
if (NTT.isLoci(loci)) {
const offsetGroupId = loci.elements[0];
return apply(Interval.ofBounds(offsetGroupId, offsetGroupId + 4));
}
return false;
}
function NtCTubeVisual(materialId: number): UnitsVisual<NtCTubeMeshParams> {
return UnitsMeshVisual<NtCTubeMeshParams>({
defaultProps: PD.getDefaultValues(NtCTubeMeshParams),
createGeometry: createNtCTubeMesh,
createLocationIterator: createNtCTubeSegmentsIterator,
getLoci: getNtCTubeSegmentLoci,
eachLocation: eachNtCTubeSegment,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<NtCTubeMeshParams>, currentProps: PD.Values<NtCTubeMeshParams>) => {
state.createGeometry = (
newProps.quality !== currentProps.quality ||
newProps.residueMarkerWidth !== currentProps.residueMarkerWidth ||
newProps.segmentBoundaryWidth !== currentProps.segmentBoundaryWidth ||
newProps.doubleSided !== currentProps.doubleSided ||
newProps.alpha !== currentProps.alpha ||
newProps.linearSegments !== currentProps.linearSegments ||
newProps.radialSegments !== currentProps.radialSegments
);
}
}, materialId);
}
const NtCTubeVisuals = {
'ntc-tube-symbol': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, NtCTubeMeshParams>) => UnitsRepresentation('NtC Tube Mesh', ctx, getParams, NtCTubeVisual),
};
export const NtCTubeParams = {
...NtCTubeMeshParams
};
export type NtCTubeParams = typeof NtCTubeParams;
export function getNtCTubeParams(ctx: ThemeRegistryContext, structure: Structure) {
return PD.clone(NtCTubeParams);
}
export type NtCTubeRepresentation = StructureRepresentation<NtCTubeParams>;
export function NtCTubeRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, NtCTubeParams>): NtCTubeRepresentation {
return Representation.createMulti('NtC Tube', ctx, getParams, StructureRepresentationStateBuilder, NtCTubeVisuals as unknown as Representation.Def<Structure, NtCTubeParams>);
}
export const NtCTubeRepresentationProvider = StructureRepresentationProvider({
name: 'ntc-tube',
label: 'NtC Tube',
description: 'Displays schematic representation of NtC conformers',
factory: NtCTubeRepresentation,
getParams: getNtCTubeParams,
defaultValues: PD.getDefaultValues(NtCTubeParams),
defaultColorTheme: { name: 'ntc-tube' },
defaultSizeTheme: { name: 'uniform', props: { value: 2.0 } },
isApplicable: (structure: Structure) => structure.models.every(m => Dnatco.isApplicable(m)),
ensureCustomProperties: {
attach: async (ctx: CustomProperty.Context, structure: Structure) => structure.models.forEach(m => NtCTubeProvider.attach(ctx, m, void 0, true)),
detach: (data) => data.models.forEach(m => NtCTubeProvider.ref(m, false)),
},
});

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { NtCTubeSegmentLabel } from './behavior';
import { DnatcoTypes } from '../types';
import { Sphere3D } from '../../../mol-math/geometry/primitives/sphere3d';
import { DataLocation } from '../../../mol-model/location';
import { DataLoci } from '../../../mol-model/loci';
export namespace NtCTubeTypes {
const DataTag = 'dnatco-tube-segment-data';
const DummyTag = 'dnatco-tube-dummy';
export type Data = {
data: DnatcoTypes.Steps,
}
export type TubeBlock = {
step: DnatcoTypes.Step,
kind: 'upper' | 'lower' | 'residue-boundary' | 'segment-boundary';
}
export interface Location extends DataLocation<TubeBlock> {}
export function Location(payload: TubeBlock) {
return DataLocation(DataTag, payload, {});
}
export function isLocation(x: any): x is Location {
return !!x && x.kind === 'data-location' && x.tag === DataTag;
}
export interface Loci extends DataLoci<DnatcoTypes.Step[], number> {}
export interface DummyLoci extends DataLoci<{}, number> {}
export function Loci(data: DnatcoTypes.Step[], stepIndices: number[], elements: number[], boundingSphere?: Sphere3D): Loci {
return DataLoci(DataTag, data, elements, boundingSphere ? () => boundingSphere : undefined, () => stepIndices[0] !== undefined ? NtCTubeSegmentLabel(data[stepIndices[0]]) : '');
}
export function DummyLoci(): DummyLoci {
return DataLoci(DummyTag, {}, [], undefined, () => '');
}
export function isLoci(x: any): x is Loci {
return !!x && x.kind === 'data-loci' && x.tag === DataTag;
}
}

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { NtCTubeTypes as NTT } from './types';
import { NtCTubeProvider } from './property';
import { DnatcoUtil } from '../util';
import { Segmentation, SortedArray } from '../../../mol-data/int';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure';
function getAtomPosition(vec: Vec3, loc: StructureElement.Location, residue: DnatcoUtil.Residue, names: string[], altId: string, insCode: string) {
const eI = DnatcoUtil.getAtomIndex(loc, residue, names, altId, insCode);
if (eI !== -1)
loc.unit.conformation.invariantPosition(eI, vec);
else {
vec[0] = 0; vec[1] = 0; vec[2] = 0;
}
}
const p_1 = Vec3();
const p0 = Vec3();
const p1 = Vec3();
const p2 = Vec3();
const p3 = Vec3();
const p4 = Vec3();
const pP = Vec3();
function getPoints(
loc: StructureElement.Location,
r0: DnatcoUtil.Residue | undefined, r1: DnatcoUtil.Residue, r2: DnatcoUtil.Residue,
altId0: string, altId1: string, altId2: string,
insCode0: string, insCode1: string, insCode2: string,
) {
if (r0) getAtomPosition(p_1, loc, r0, ['C5\'', 'C5*'], altId0, insCode0);
r0 ? getAtomPosition(p0, loc, r0, ['O3\'', 'O3*'], altId0, insCode0) : getAtomPosition(p0, loc, r1, ['O5\'', 'O5*'], altId1, insCode1);
getAtomPosition(p1, loc, r1, ['C5\'', 'C5*'], altId1, insCode1);
getAtomPosition(p2, loc, r1, ['O3\'', 'O3*'], altId1, insCode1);
getAtomPosition(p3, loc, r2, ['C5\'', 'C5*'], altId2, insCode2);
getAtomPosition(p4, loc, r2, ['O3\'', 'O3*'], altId2, insCode2);
getAtomPosition(pP, loc, r2, ['P'], altId2, insCode2);
return { p_1, p0, p1, p2, p3, p4, pP };
}
function hasGapElements(r: DnatcoUtil.Residue, unit: Unit) {
for (let xI = r.start; xI < r.end; xI++) {
const eI = unit.elements[xI];
if (SortedArray.has(unit.gapElements, eI)) {
return true;
}
}
return false;
}
export type NtCTubeSegment = {
p_1: Vec3,
p0: Vec3,
p1: Vec3,
p2: Vec3,
p3: Vec3,
p4: Vec3,
pP: Vec3,
stepIdx: number,
followsGap: boolean,
firstInChain: boolean,
capEnd: boolean,
}
export class NtCTubeSegmentsIterator {
private chainIt: Segmentation.SegmentIterator<ChainIndex>;
private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
/* Second residue of the previous step, may be undefined
* if we are at the beginning of a chain or right after a discontinuity */
private residuePrev?: DnatcoUtil.Residue;
/* First residue of the current step */
private residueOne?: DnatcoUtil.Residue;
/* Second residue of the current step */
private residueTwo: DnatcoUtil.Residue;
/* First residue of the next step, may be undefined
* if we are at the end of a chain.
* Undefined value indicates that the iterator has reached the end.*/
private residueNext?: DnatcoUtil.Residue;
private data?: NTT.Data;
private altIdOne = '';
private insCodeOne = '';
private loc: StructureElement.Location;
private moveStep() {
if (!this.residueNext)
return void 0;
/* Assume discontinuity of the ResidueIndex of the residue that would become residue one (= first residue of the corresponding step)
* does not equal to ResidueIndex of what would be residue two (= second residue of the corresponding step). */
if (this.residueTwo.index + 1 === this.residueNext.index) {
this.residuePrev = DnatcoUtil.copyResidue(this.residueOne);
this.residueOne = DnatcoUtil.copyResidue(this.residueTwo);
this.residueTwo = DnatcoUtil.copyResidue(this.residueNext)!;
this.residueNext = this.residueIt.hasNext ? DnatcoUtil.copyResidue(this.residueIt.move())! : void 0;
} else {
if (!this.residueIt.hasNext) {
this.residueNext = void 0;
return void 0;
}
// There is discontinuity, act as if we were at the beginning of a chain
this.residuePrev = void 0;
this.residueOne = DnatcoUtil.copyResidue(this.residueNext);
this.residueTwo = DnatcoUtil.copyResidue(this.residueIt.move())!;
this.residueNext = this.residueIt.hasNext ? DnatcoUtil.copyResidue(this.residueIt.move())! : void 0;
}
return this.toSegment(this.residuePrev, this.residueOne!, this.residueTwo, this.residueNext);
}
private prime() {
if (this.residueIt.hasNext)
this.residueTwo = DnatcoUtil.copyResidue(this.residueIt.move())!;
if (this.residueIt.hasNext)
this.residueNext = this.residueIt.move();
}
private toSegment(r0: DnatcoUtil.Residue | undefined, r1: DnatcoUtil.Residue, r2: DnatcoUtil.Residue, r3: DnatcoUtil.Residue | undefined): NtCTubeSegment | undefined {
const indices = DnatcoUtil.getStepIndices(this.data!.data, this.loc, r1);
if (indices.length === 0)
return void 0;
const stepIdx = indices[0];
const step = this.data!.data.steps[stepIdx];
const altIdPrev = this.altIdOne;
const insCodePrev = this.insCodeOne;
this.altIdOne = step.label_alt_id_1;
this.insCodeOne = step.PDB_ins_code_1;
const altIdTwo = step.label_alt_id_2;
const insCodeTwo = step.PDB_ins_code_2;
const followsGap = !!r0 && hasGapElements(r0, this.loc.unit) && hasGapElements(r1, this.loc.unit);
const precedesDiscontinuity = r3 ? r3.index !== r2.index + 1 : false;
return {
...getPoints(this.loc, r0, r1, r2, altIdPrev, this.altIdOne, altIdTwo, insCodePrev, this.insCodeOne, insCodeTwo),
stepIdx,
followsGap,
firstInChain: !r0,
capEnd: !this.residueNext || precedesDiscontinuity || hasGapElements(r2, this.loc.unit),
};
}
constructor(structure: Structure, unit: Unit.Atomic) {
this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
const prop = NtCTubeProvider.get(unit.model).value;
this.data = prop?.data;
if (this.chainIt.hasNext) {
this.residueIt.setSegment(this.chainIt.move());
this.prime();
}
this.loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
}
get hasNext() {
if (!this.data)
return false;
return !!this.residueNext
? true
: this.chainIt.hasNext;
}
move() {
if (!!this.residueNext) {
return this.moveStep();
} else {
this.residuePrev = void 0; // Assume discontinuity when we switch chains
this.residueNext = void 0;
this.residueIt.setSegment(this.chainIt.move());
this.prime();
return this.moveStep();
}
}
}

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { DnatcoTypes } from './types';
import { Column, Table } from '../../mol-data/db';
import { toTable } from '../../mol-io/reader/cif/schema';
import { Model } from '../../mol-model/structure';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export type DnatcoSteps = PropertyWrapper<DnatcoTypes.Steps | undefined>;
export const DnatcoParams = {};
export type DnatcoParams = typeof DnatcoParams;
export type DnatcoProps = PD.Values<DnatcoParams>;
export namespace Dnatco {
export const Schema = {
ndb_struct_ntc_step: {
id: Column.Schema.int,
name: Column.Schema.str,
PDB_model_number: Column.Schema.int,
label_entity_id_1: Column.Schema.int,
label_asym_id_1: Column.Schema.str,
label_seq_id_1: Column.Schema.int,
label_comp_id_1: Column.Schema.str,
label_alt_id_1: Column.Schema.str,
label_entity_id_2: Column.Schema.int,
label_asym_id_2: Column.Schema.str,
label_seq_id_2: Column.Schema.int,
label_comp_id_2: Column.Schema.str,
label_alt_id_2: Column.Schema.str,
auth_asym_id_1: Column.Schema.str,
auth_seq_id_1: Column.Schema.int,
auth_asym_id_2: Column.Schema.str,
auth_seq_id_2: Column.Schema.int,
PDB_ins_code_1: Column.Schema.str,
PDB_ins_code_2: Column.Schema.str,
},
ndb_struct_ntc_step_summary: {
step_id: Column.Schema.int,
assigned_CANA: Column.Schema.str,
assigned_NtC: Column.Schema.str,
confal_score: Column.Schema.int,
euclidean_distance_NtC_ideal: Column.Schema.float,
cartesian_rmsd_closest_NtC_representative: Column.Schema.float,
closest_CANA: Column.Schema.str,
closest_NtC: Column.Schema.str,
closest_step_golden: Column.Schema.str
}
};
export type Schema = typeof Schema;
export function getStepsFromCif(
model: Model,
cifSteps: Table<typeof Dnatco.Schema.ndb_struct_ntc_step>,
stepsSummary: StepsSummaryTable
): DnatcoTypes.Steps {
const steps = new Array<DnatcoTypes.Step>();
const mapping = new Array<DnatcoTypes.MappedChains>();
const {
id, PDB_model_number, name,
auth_asym_id_1, auth_seq_id_1, label_comp_id_1, label_alt_id_1, PDB_ins_code_1,
auth_asym_id_2, auth_seq_id_2, label_comp_id_2, label_alt_id_2, PDB_ins_code_2,
_rowCount
} = cifSteps;
if (_rowCount !== stepsSummary._rowCount) throw new Error('Inconsistent mmCIF data');
for (let i = 0; i < _rowCount; i++) {
const {
NtC,
confal_score,
rmsd
} = getSummaryData(id.value(i), i, stepsSummary);
const modelNum = PDB_model_number.value(i);
const chainId = auth_asym_id_1.value(i);
const seqId = auth_seq_id_1.value(i);
const modelIdx = modelNum - 1;
if (mapping.length <= modelIdx || !mapping[modelIdx])
mapping[modelIdx] = new Map<string, DnatcoTypes.MappedResidues>();
const step = {
PDB_model_number: modelNum,
name: name.value(i),
auth_asym_id_1: chainId,
auth_seq_id_1: seqId,
label_comp_id_1: label_comp_id_1.value(i),
label_alt_id_1: label_alt_id_1.value(i),
PDB_ins_code_1: PDB_ins_code_1.value(i),
auth_asym_id_2: auth_asym_id_2.value(i),
auth_seq_id_2: auth_seq_id_2.value(i),
label_comp_id_2: label_comp_id_2.value(i),
label_alt_id_2: label_alt_id_2.value(i),
PDB_ins_code_2: PDB_ins_code_2.value(i),
confal_score,
NtC,
rmsd,
};
steps.push(step);
const mappedChains = mapping[modelIdx];
const residuesOnChain = mappedChains.get(chainId) ?? new Map<number, number[]>();
const stepsForResidue = residuesOnChain.get(seqId) ?? [];
stepsForResidue.push(steps.length - 1);
residuesOnChain.set(seqId, stepsForResidue);
mappedChains.set(chainId, residuesOnChain);
mapping[modelIdx] = mappedChains;
}
return { steps, mapping };
}
export async function fromCif(ctx: CustomProperty.Context, model: Model, props: DnatcoProps): Promise<CustomProperty.Data<DnatcoSteps>> {
const info = PropertyWrapper.createInfo();
const data = getCifData(model);
if (data === undefined) return { value: { info, data: undefined } };
const fromCif = getStepsFromCif(model, data.steps, data.stepsSummary);
return { value: { info, data: fromCif } };
}
export function getCifData(model: Model) {
if (!MmcifFormat.is(model.sourceData)) throw new Error('Data format must be mmCIF');
if (!hasNdbStructNtcCategories(model)) return undefined;
return {
steps: toTable(Schema.ndb_struct_ntc_step, model.sourceData.data.frame.categories.ndb_struct_ntc_step),
stepsSummary: toTable(Schema.ndb_struct_ntc_step_summary, model.sourceData.data.frame.categories.ndb_struct_ntc_step_summary)
};
}
function hasNdbStructNtcCategories(model: Model): boolean {
if (!MmcifFormat.is(model.sourceData)) return false;
const names = (model.sourceData).data.frame.categoryNames;
return names.includes('ndb_struct_ntc_step') && names.includes('ndb_struct_ntc_step_summary');
}
export function isApplicable(model?: Model): boolean {
return !!model && hasNdbStructNtcCategories(model);
}
}
type StepsSummaryTable = Table<typeof Dnatco.Schema.ndb_struct_ntc_step_summary>;
function getSummaryData(id: number, i: number, stepsSummary: StepsSummaryTable) {
const {
step_id,
confal_score,
assigned_NtC,
cartesian_rmsd_closest_NtC_representative,
} = stepsSummary;
// Assume that step_ids in ntc_step_summary are in the same order as steps in ntc_step
for (let j = i; j < stepsSummary._rowCount; j++) {
if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) };
}
// Safety net for cases where the previous assumption is not met
for (let j = 0; j < i; j++) {
if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) };
}
throw new Error('Inconsistent mmCIF data');
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
export namespace DnatcoTypes {
export const DataTag = 'dnatco-confal-half-step';
export type Step = {
PDB_model_number: number,
name: string,
auth_asym_id_1: string,
auth_seq_id_1: number,
label_comp_id_1: string,
label_alt_id_1: string,
PDB_ins_code_1: string,
auth_asym_id_2: string,
auth_seq_id_2: number,
label_comp_id_2: string,
label_alt_id_2: string,
PDB_ins_code_2: string,
confal_score: number,
NtC: string,
rmsd: number,
}
export type MappedChains = Map<string, MappedResidues>;
export type MappedResidues = Map<number, number[]>;
export interface Steps {
steps: Array<Step>,
mapping: MappedChains[],
}
export interface HalfStep {
step: Step,
isLower: boolean,
}
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { DnatcoTypes } from './types';
import { OrderedSet, Segmentation } from '../../mol-data/int';
import { EmptyLoci } from '../../mol-model/loci';
import { ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
const EmptyStepIndices = new Array<number>();
export namespace DnatcoUtil {
export type Residue = Segmentation.Segment<ResidueIndex>;
export function copyResidue(r?: Residue) {
return r ? { index: r.index, start: r.start, end: r.end } : void 0;
}
export function getAtomIndex(loc: StructureElement.Location, residue: Residue, names: string[], altId: string, insCode: string): ElementIndex {
for (let eI = residue.start; eI < residue.end; eI++) {
loc.element = loc.unit.elements[eI];
const elName = StructureProperties.atom.label_atom_id(loc);
const elAltId = StructureProperties.atom.label_alt_id(loc);
const elInsCode = StructureProperties.residue.pdbx_PDB_ins_code(loc);
if (names.includes(elName) && (elAltId === altId || elAltId.length === 0) && (elInsCode === insCode))
return loc.element;
}
return -1 as ElementIndex;
}
export function getStepIndices(data: DnatcoTypes.Steps, loc: StructureElement.Location, r: DnatcoUtil.Residue) {
loc.element = loc.unit.elements[r.start];
const modelIdx = StructureProperties.unit.model_num(loc) - 1;
const chainId = StructureProperties.chain.auth_asym_id(loc);
const seqId = StructureProperties.residue.auth_seq_id(loc);
const insCode = StructureProperties.residue.pdbx_PDB_ins_code(loc);
const chains = data.mapping[modelIdx];
if (!chains) return EmptyStepIndices;
const residues = chains.get(chainId);
if (!residues) return EmptyStepIndices;
const indices = residues.get(seqId);
if (!indices) return EmptyStepIndices;
return insCode !== '' ? indices.filter(idx => data.steps[idx].PDB_ins_code_1 === insCode) : indices;
}
export function residueAltIds(structure: Structure, unit: Unit, residue: Residue) {
const altIds = new Array<string>();
const loc = StructureElement.Location.create(structure, unit);
for (let eI = residue.start; eI < residue.end; eI++) {
loc.element = OrderedSet.getAt(unit.elements, eI);
const altId = StructureProperties.atom.label_alt_id(loc);
if (altId !== '' && !altIds.includes(altId))
altIds.push(altId);
}
return altIds;
}
const _loc = StructureElement.Location.create();
export function residueToLoci(asymId: string, seqId: number, altId: string | undefined, insCode: string, loci: StructureElement.Loci, source: 'label' | 'auth') {
_loc.structure = loci.structure;
for (const e of loci.elements) {
_loc.unit = e.unit;
const getAsymId = source === 'label' ? StructureProperties.chain.label_asym_id : StructureProperties.chain.auth_asym_id;
const getSeqId = source === 'label' ? StructureProperties.residue.label_seq_id : StructureProperties.residue.auth_seq_id;
// Walk the entire unit and look for the requested residue
const chainIt = Segmentation.transientSegments(e.unit.model.atomicHierarchy.chainAtomSegments, e.unit.elements);
const residueIt = Segmentation.transientSegments(e.unit.model.atomicHierarchy.residueAtomSegments, e.unit.elements);
const elemIndex = (idx: number) => OrderedSet.getAt(e.unit.elements, idx);
while (chainIt.hasNext) {
const chain = chainIt.move();
_loc.element = elemIndex(chain.start);
const _asymId = getAsymId(_loc);
if (_asymId !== asymId)
continue; // Wrong chain, skip it
residueIt.setSegment(chain);
while (residueIt.hasNext) {
const residue = residueIt.move();
_loc.element = elemIndex(residue.start);
const _seqId = getSeqId(_loc);
if (_seqId === seqId) {
const _insCode = StructureProperties.residue.pdbx_PDB_ins_code(_loc);
if (_insCode !== insCode)
continue;
if (altId) {
const _altIds = residueAltIds(loci.structure, e.unit, residue);
if (!_altIds.includes(altId))
continue;
}
const start = residue.start as StructureElement.UnitIndex;
const end = residue.end as StructureElement.UnitIndex;
return StructureElement.Loci(
loci.structure,
[{ unit: e.unit, indices: OrderedSet.ofBounds(start, end) }]
);
}
}
}
}
return EmptyLoci;
}
}

View File

@@ -0,0 +1,187 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
/** Testing examples for using mesh-extension.ts. */
import { CIF } from '../../mol-io/reader/cif';
import { Volume } from '../../mol-model/volume';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { ParamDefinition } from '../../mol-util/param-definition';
import { createMeshFromUrl } from './mesh-extension';
import { MeshServerInfo } from './mesh-streaming/server-info';
import { InitMeshStreaming } from './mesh-streaming/transformers';
import * as MeshUtils from './mesh-utils';
export const DB_URL = '/db'; // local
export async function runMeshExtensionExamples(plugin: PluginContext, db_url: string = DB_URL) {
console.time('TIME MESH EXAMPLES');
// await runIsosurfaceExample(plugin, db_url);
// await runMolsurfaceExample(plugin);
// Focused Ion Beam-Scanning Electron Microscopy of mitochondrial reticulum in murine skeletal muscle: https://www.ebi.ac.uk/empiar/EMPIAR-10070/
// await runMeshExample(plugin, 'all', db_url);
// await runMeshExample(plugin, 'fg', db_url);
// await runMultimeshExample(plugin, 'fg', 'worst', db_url);
// await runCifMeshExample(plugin);
// await runMeshExample2(plugin, 'fg');
await runMeshStreamingExample(plugin);
console.timeEnd('TIME MESH EXAMPLES');
}
/** Example for downloading multiple separate segments, each containing 1 mesh. */
export async function runMeshExample(plugin: PluginContext, segments: 'fg' | 'all', db_url: string = DB_URL) {
const detail = 2;
const segmentIds = (segments === 'all') ?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17] // segment-16 has no detail-2
: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 17]; // segment-13 and segment-15 are quasi background
for (const segmentId of segmentIds) {
await createMeshFromUrl(plugin, `${db_url}/empiar-10070-mesh-rounded/segment-${segmentId}/detail-${detail}`, segmentId, detail, true, undefined);
}
}
/** Example for downloading multiple separate segments, each containing 1 mesh. */
export async function runMeshExample2(plugin: PluginContext, segments: 'one' | 'few' | 'fg' | 'all') {
const detail = 1;
const segmentIds = (segments === 'one') ? [15]
: (segments === 'few') ? [1, 4, 7, 10, 16]
: (segments === 'all') ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17] // segment-16 has no detail-2
: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 17]; // segment-13 and segment-15 are quasi background
for (const segmentId of segmentIds) {
await createMeshFromUrl(plugin, `http://localhost:9000/v2/empiar/empiar-10070/mesh_bcif/${segmentId}/${detail}`, segmentId, detail, false, undefined);
}
}
/** Example for downloading a single segment containing multiple meshes. */
export async function runMultimeshExample(plugin: PluginContext, segments: 'fg' | 'all', detailChoice: 'best' | 'worst', db_url: string = DB_URL) {
const urlDetail = (detailChoice === 'best') ? '2' : 'worst';
const numDetail = (detailChoice === 'best') ? 2 : 1000;
await createMeshFromUrl(plugin, `${db_url}/empiar-10070-multimesh-rounded/segments-${segments}/detail-${urlDetail}`, 0, numDetail, false, undefined);
}
export async function runMeshStreamingExample(plugin: PluginContext, source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070', serverUrl?: string, parent?: StateObjectSelector) {
const params = ParamDefinition.getDefaultValues(MeshServerInfo.Params);
if (serverUrl) params.serverUrl = serverUrl;
params.source = source;
params.entryId = entryId;
await plugin.runTask(plugin.state.data.applyAction(InitMeshStreaming, params, parent?.ref), { useOverlay: false });
}
/** Example for downloading a protein structure and visualizing molecular surface. */
export async function runMolsurfaceExample(plugin: PluginContext) {
const entryId = 'pdb-7etq';
// Node "https://www.ebi.ac.uk/pdbe/entry-files/download/7etq.bcif" ("transformer": "ms-plugin.download") -> var data
const data = await plugin.builders.data.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/download/7etq.bcif', isBinary: true }, { state: { isGhost: false } });
console.log('formats:', plugin.dataFormats.list);
// Node "CIF File" ("transformer": "ms-plugin.parse-cif")
// Node "7ETQ 1 model" ("transformer": "ms-plugin.trajectory-from-mmcif") -> var trajectory
const parsed = await plugin.dataFormats.get('mmcif')!.parse(plugin, data, { entryId });
const trajectory: StateObjectSelector<PluginStateObject.Molecule.Trajectory> = parsed.trajectory;
console.log('parsed', parsed);
console.log('trajectory', trajectory);
// Node "Model 1" ("transformer": "ms-plugin.model-from-trajectory") -> var model
const model = await plugin.build().to(trajectory).apply(StateTransforms.Model.ModelFromTrajectory).commit();
console.log('model:', model);
// Node "Model 91 elements" ("transformer": "ms-plugin.structure-from-model") -> var structure
const structure = await plugin.build().to(model).apply(StateTransforms.Model.StructureFromModel,).commit();
console.log('structure:', structure);
// Node "Molecular Surface" ("transformer": "ms-plugin.structure-representation-3d") -> var repr
const reprParams = createStructureRepresentationParams(plugin, undefined, { type: 'molecular-surface' });
const repr = await plugin.build().to(structure).apply(StateTransforms.Representation.StructureRepresentation3D, reprParams).commit();
console.log('repr:', repr);
}
/** Example for downloading an EMDB density data and visualizing isosurface. */
export async function runIsosurfaceExample(plugin: PluginContext, db_url: string = DB_URL) {
const entryId = 'emd-1832';
const isoLevel = 2.73;
let root = await plugin.build();
const data = await plugin.builders.data.download({ url: `${db_url}/emd-1832-box`, isBinary: true }, { state: { isGhost: false } });
const parsed = await plugin.dataFormats.get('dscif')!.parse(plugin, data, { entryId });
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[0] ?? parsed.volume;
const volumeData = volume.cell!.obj!.data;
console.log('data:', data);
console.log('parsed:', parsed);
console.log('volume:', volume);
console.log('volumeData:', volumeData);
root = await plugin.build();
console.log('root:', root);
console.log('to:', root.to(volume));
console.log('toRoot:', root.toRoot());
let volumeParams;
volumeParams = createVolumeRepresentationParams(plugin, volumeData, {
type: 'isosurface',
typeParams: {
alpha: 0.5,
isoValue: Volume.adjustedIsoValue(volumeData, isoLevel, 'relative'),
visuals: ['solid'],
sizeFactor: 1,
},
color: 'uniform',
colorParams: { value: Color(0x00aaaa) },
});
root.to(volume).apply(StateTransforms.Representation.VolumeRepresentation3D, volumeParams);
volumeParams = createVolumeRepresentationParams(plugin, volumeData, {
type: 'isosurface',
typeParams: {
alpha: 1.0,
isoValue: Volume.adjustedIsoValue(volumeData, isoLevel, 'relative'),
visuals: ['wireframe'],
sizeFactor: 1,
},
color: 'uniform',
colorParams: { value: Color(0x8800aa) },
});
root.to(volume).apply(StateTransforms.Representation.VolumeRepresentation3D, volumeParams);
await root.commit();
}
export async function runCifMeshExample(plugin: PluginContext, api: string = 'http://localhost:9000/v2',
source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070',
segmentId: number = 1, detail: number = 10,
) {
const url = `${api}/${source}/${entryId}/mesh_bcif/${segmentId}/${detail}`;
getMeshFromBcif(plugin, url);
}
async function getMeshFromBcif(plugin: PluginContext, url: string) {
const urlAsset = Asset.getUrlAsset(plugin.managers.asset, url);
const asset = await plugin.runTask(plugin.managers.asset.resolve(urlAsset, 'binary'));
const parsed = await plugin.runTask(CIF.parseBinary(asset.data));
if (parsed.isError) {
plugin.log.error('VolumeStreaming, parsing CIF: ' + parsed.toString());
return;
}
console.log('blocks:', parsed.result.blocks);
const mesh = await MeshUtils.meshFromCif(parsed.result);
console.log(mesh);
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Column, Database } from '../../mol-data/db';
import { CifFrame } from '../../mol-io/reader/cif';
import { toDatabase } from '../../mol-io/reader/cif/schema';
const int = Column.Schema.int;
const float = Column.Schema.float;
// TODO in future, move to molstar/src/mol-io/reader/cif/schema/mesh.ts
export const Mesh_Data_Schema = {
mesh: {
id: int,
},
mesh_vertex: {
mesh_id: int,
vertex_id: int,
x: float,
y: float,
z: float,
},
/** Table of triangles, 3 rows per triangle */
mesh_triangle: {
mesh_id: int,
/** Indices of vertices within mesh */
vertex_id: int,
}
};
export type Mesh_Data_Schema = typeof Mesh_Data_Schema;
export interface Mesh_Data_Database extends Database<Mesh_Data_Schema> {}
// TODO in future, move to molstar/src/mol-io/reader/cif.ts: CIF.schema.mesh
export const CIF_schema_mesh = (frame: CifFrame) => toDatabase<Mesh_Data_Schema, Mesh_Data_Database>(Mesh_Data_Schema, frame);

View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
/** Defines new types of State tree transformers for dealing with mesh data. */
import { BaseGeometry, VisualQuality, VisualQualityOptions } from '../../mol-geo/geometry/base';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { CifFile } from '../../mol-io/reader/cif';
import { Box3D } from '../../mol-math/geometry';
import { Vec3 } from '../../mol-math/linear-algebra';
import { Shape } from '../../mol-model/shape';
import { ShapeProvider } from '../../mol-model/shape/provider';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { Download } from '../../mol-plugin-state/transforms/data';
import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectRef, StateObjectSelector, StateTransformer } from '../../mol-state';
import { Task } from '../../mol-task';
import { Color } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import * as MeshUtils from './mesh-utils';
export const BACKGROUND_OPACITY = 0.2;
export const FOREROUND_OPACITY = 1;
export const VolsegTransform: StateTransformer.Builder.Root = StateTransformer.builderFactory('volseg');
// // // // // // // // // // // // // // // // // // // // // // // //
// Parsed data
/** Data type for `MeshlistStateObject` - list of meshes */
export interface MeshlistData {
segmentId: number,
segmentName: string,
detail: number,
meshIds: number[],
mesh: Mesh,
/** Reference to the object which created this meshlist (e.g. `MeshStreaming.Behavior`) */
ownerId?: string,
}
export namespace MeshlistData {
export function empty(): MeshlistData {
return {
segmentId: 0,
segmentName: 'Empty',
detail: 0,
meshIds: [],
mesh: Mesh.createEmpty(),
};
};
export async function fromCIF(data: CifFile, segmentId: number, segmentName: string, detail: number): Promise<MeshlistData> {
const { mesh, meshIds } = await MeshUtils.meshFromCif(data);
return {
segmentId,
segmentName,
detail,
meshIds,
mesh,
};
}
export function stats(meshListData: MeshlistData): string {
return `Meshlist "${meshListData.segmentName}" (detail ${meshListData.detail}): ${meshListData.meshIds.length} meshes, ${meshListData.mesh.vertexCount} vertices, ${meshListData.mesh.triangleCount} triangles`;
}
export function getShape(data: MeshlistData, color: Color): Shape<Mesh> {
const mesh = data.mesh;
const meshShape: Shape<Mesh> = Shape.create(data.segmentName, data, mesh,
() => color,
() => 1,
// group => `${data.segmentName} | Segment ${data.segmentId} | Detail ${data.detail} | Mesh ${group}`,
group => data.segmentName,
);
return meshShape;
}
export function combineBBoxes(boxes: (Box3D | null)[]): Box3D | null {
let result = null;
for (const box of boxes) {
if (!box) continue;
if (result) {
Vec3.min(result.min, result.min, box.min);
Vec3.max(result.max, result.max, box.max);
} else {
result = Box3D.zero();
Box3D.copy(result, box);
}
}
return result;
}
export function bbox(data: MeshlistData): Box3D | null {
return MeshUtils.bbox(data.mesh);
}
export function allVerticesUsed(data: MeshlistData): boolean {
const unusedVertices = new Set();
for (let i = 0; i < data.mesh.vertexCount; i++) {
unusedVertices.add(i);
}
for (let i = 0; i < 3 * data.mesh.triangleCount; i++) {
const v = data.mesh.vertexBuffer.ref.value[i];
unusedVertices.delete(v);
}
return unusedVertices.size === 0;
}
}
// // // // // // // // // // // // // // // // // // // // // // // //
// Raw Data -> Parsed data
export class MeshlistStateObject extends PluginStateObject.Create<MeshlistData>({ name: 'Parsed Meshlist', typeClass: 'Object' }) { }
export const ParseMeshlistTransformer = VolsegTransform({
name: 'meshlist-from-string',
from: PluginStateObject.Format.Cif,
to: MeshlistStateObject,
params: {
label: PD.Text(MeshlistStateObject.type.name, { isHidden: true }),
segmentId: PD.Numeric(1, {}, { isHidden: true }),
segmentName: PD.Text('Segment'),
detail: PD.Numeric(1, {}, { isHidden: true }),
/** Reference to the object which manages this meshlist (e.g. `MeshStreaming.Behavior`) */
ownerId: PD.Text('', { isHidden: true }),
}
})({
apply({ a, params }, globalCtx) { // `a` is the parent node, params are 2nd argument to To.apply(), `globalCtx` is the plugin
return Task.create('Create Parsed Meshlist', async ctx => {
const meshlistData = await MeshlistData.fromCIF(a.data, params.segmentId, params.segmentName, params.detail);
meshlistData.ownerId = params.ownerId;
const es = meshlistData.meshIds.length === 1 ? '' : 'es';
return new MeshlistStateObject(meshlistData, { label: params.label, description: `${meshlistData.segmentName} (${meshlistData.meshIds.length} mesh${es})` });
});
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
// Parsed data -> Shape
/** Data type for PluginStateObject.Shape.Provider */
type MeshShapeProvider = ShapeProvider<MeshlistData, Mesh, Mesh.Params>;
namespace MeshShapeProvider {
export function fromMeshlistData(meshlist: MeshlistData, color?: Color): MeshShapeProvider {
const theColor = color ?? MeshUtils.ColorGenerator.next().value;
return {
label: 'Mesh',
data: meshlist,
params: meshShapeProviderParams,
geometryUtils: Mesh.Utils,
getShape: (ctx, data: MeshlistData) => MeshlistData.getShape(data, theColor),
};
}
}
const meshShapeProviderParams: Mesh.Params = {
...Mesh.Params,
quality: PD.Select<VisualQuality>('custom', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }), // use 'custom' when wanting to apply doubleSided
doubleSided: PD.Boolean(true, BaseGeometry.CustomQualityParamInfo),
// set `flatShaded`: true to see the real mesh vertices and triangles
transparentBackfaces: PD.Select('on', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory), // 'on' means: show backfaces with correct opacity, even when opacity < 1 (requires doubleSided) ¯\_(ツ)_/¯
};
export const MeshShapeTransformer = VolsegTransform({
name: 'shape-from-meshlist',
display: { name: 'Shape from Meshlist', description: 'Create Shape from Meshlist data' },
from: MeshlistStateObject,
to: PluginStateObject.Shape.Provider,
params: {
color: PD.Value<Color | undefined>(undefined), // undefined means random color
},
})({
apply({ a, params }) {
const shapeProvider = MeshShapeProvider.fromMeshlistData(a.data, params.color);
return new PluginStateObject.Shape.Provider(shapeProvider, { label: PluginStateObject.Shape.Provider.type.name, description: a.description });
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
/** Download data and create state tree hierarchy down to visual representation. */
export async function createMeshFromUrl(plugin: PluginContext, meshDataUrl: string, segmentId: number, detail: number,
collapseTree: boolean, color?: Color, parent?: StateObjectSelector | StateObjectRef, transparentIfBboxAbove?: number,
name?: string, ownerId?: string) {
const update = parent ? plugin.build().to(parent) : plugin.build().toRoot();
const rawDataNodeRef = update.apply(Download,
{ url: meshDataUrl, isBinary: true, label: `Downloaded Data ${segmentId}` },
{ state: { isCollapsed: collapseTree } }
).ref;
const parsedDataNode = await update.to(rawDataNodeRef)
.apply(StateTransforms.Data.ParseCif)
.apply(ParseMeshlistTransformer,
{ label: undefined, segmentId: segmentId, segmentName: name ?? `Segment ${segmentId}`, detail: detail, ownerId: ownerId },
{}
)
.commit();
let transparent = false;
if (transparentIfBboxAbove !== undefined && parsedDataNode.data) {
const bbox = MeshlistData.bbox(parsedDataNode.data) || Box3D.zero();
transparent = Box3D.volume(bbox) > transparentIfBboxAbove;
}
await plugin.build().to(parsedDataNode)
.apply(MeshShapeTransformer, { color: color },)
.apply(ShapeRepresentation3D,
{ alpha: transparent ? BACKGROUND_OPACITY : FOREROUND_OPACITY },
{ tags: ['mesh-segment-visual', `segment-${segmentId}`] }
)
.commit();
return rawDataNodeRef;
}

View File

@@ -0,0 +1,332 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { distinctUntilChanged, map } from 'rxjs';
import { CIF } from '../../../mol-io/reader/cif';
import { Box3D } from '../../../mol-math/geometry';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { PluginBehavior } from '../../../mol-plugin/behavior';
import { PluginCommand } from '../../../mol-plugin/command';
import { PluginCommands } from '../../../mol-plugin/commands';
import { PluginContext } from '../../../mol-plugin/context';
import { UUID } from '../../../mol-util';
import { Asset } from '../../../mol-util/assets';
import { Color } from '../../../mol-util/color';
import { ColorNames } from '../../../mol-util/color/names';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Choice } from '../../volumes-and-segmentations/helpers';
import { MetadataWrapper } from '../../volumes-and-segmentations/volseg-api/utils';
import { MeshlistData } from '../mesh-extension';
import { MeshServerInfo } from './server-info';
const DEFAULT_SEGMENT_NAME = 'Untitled segment';
const DEFAULT_SEGMENT_COLOR = ColorNames.lightgray;
export const NO_SEGMENT = -1;
/** Maximum (worst) detail level available in GUI (TODO set actual maximum possible value) */
const MAX_DETAIL = 10;
const DEFAULT_DETAIL = 7; // TODO decide a reasonable default
/** Segments whose bounding box volume is above this value (relative to the overall bounding box) are considered as background segments */
export const BACKGROUND_SEGMENT_VOLUME_THRESHOLD = 0.5;
export class MeshStreaming extends PluginStateObject.CreateBehavior<MeshStreaming.Behavior>({ name: 'Mesh Streaming' }) { }
export namespace MeshStreaming {
export namespace Params {
export const ViewTypeChoice = new Choice({ off: 'Off', select: 'Select', all: 'All' }, 'select'); // TODO add camera target?
export type ViewType = Choice.Values<typeof ViewTypeChoice>;
export function create(options: MeshServerInfo.Data) {
return {
view: PD.MappedStatic('select', {
'off': PD.Group({}),
'select': PD.Group({
baseDetail: PD.Numeric(DEFAULT_DETAIL, { min: 1, max: MAX_DETAIL, step: 1 }, { description: 'Detail level for the non-selected segments (lower number = better)' }),
focusDetail: PD.Numeric(1, { min: 1, max: MAX_DETAIL, step: 1 }, { description: 'Detail level for the selected segment (lower number = better)' }),
selectedSegment: PD.Numeric(NO_SEGMENT, {}, { isHidden: true }),
}, { isFlat: true }),
'all': PD.Group({
detail: PD.Numeric(DEFAULT_DETAIL, { min: 1, max: MAX_DETAIL, step: 1 }, { description: 'Detail level for all segments (lower number = better)' })
}, { isFlat: true }),
}, { description: '"Off" hides all segments. \n"Select" shows all segments in lower detail, clicked segment in better detail. "All" shows all segment in the same level.' }),
};
}
export type Definition = ReturnType<typeof create>
export type Values = PD.Values<Definition>
export function copyValues(params: Values): Values {
return {
view: {
name: params.view.name,
params: { ...params.view.params } as any,
}
};
}
export function valuesEqual(p: Values, q: Values): boolean {
if (p.view.name !== q.view.name) return false;
for (const key in p.view.params) {
if ((p.view.params as any)[key] !== (q.view.params as any)[key]) return false;
}
return true;
}
export function detailsEqual(p: Values, q: Values): boolean {
switch (p.view.name) {
case 'off':
return q.view.name === 'off';
case 'select':
return q.view.name === 'select' && p.view.params.baseDetail === q.view.params.baseDetail && p.view.params.focusDetail === q.view.params.focusDetail;
case 'all':
return q.view.name === 'all' && p.view.params.detail === q.view.params.detail;
default:
throw new Error('Not implemented');
}
}
}
export interface VisualInfo {
tag: string, // e.g. high-2, low-1 // ? remove if can be omitted
segmentId: number, // ? remove if unused
segmentName: string, // ? remove if unused
detailType: VisualInfo.DetailType, // ? remove if unused
detail: number, // ? remove if unused
color: Color, // move to MeshlistData?
visible: boolean,
data?: MeshlistData,
}
export namespace VisualInfo {
export type DetailType = 'low' | 'high';
export const DetailTypes: DetailType[] = ['low', 'high'];
export function tagFor(segmentId: number, detail: DetailType) {
return `${detail}-${segmentId}`;
}
}
export class Behavior extends PluginBehavior.WithSubscribers<Params.Values> {
private id: string;
private ref: string = '';
public parentData: MeshServerInfo.Data;
private metadata?: MetadataWrapper;
public visuals?: { [tag: string]: VisualInfo };
public backgroundSegments: { [segmentId: number]: boolean } = {};
private focusObservable = this.plugin.behaviors.interaction.click.pipe( // QUESTION is this OK way to get focused segment?
map(evt => evt.current.loci),
map(loci => (loci.kind === 'group-loci') ? loci.shape.sourceData as MeshlistData : null),
map(data => (data?.ownerId === this.id) ? data : null), // do not process shapes created by others
distinctUntilChanged((old, current) => old?.segmentId === current?.segmentId),
);
private focusSubscription?: PluginCommand.Subscription = undefined;
private backgroundSegmentsInitialized = false;
constructor(plugin: PluginContext, data: MeshServerInfo.Data, params: Params.Values) {
super(plugin, params);
this.id = UUID.create22();
this.parentData = data;
}
register(ref: string): void {
this.ref = ref;
}
unregister(): void {
if (this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = undefined;
}
// TODO empty cache here (if used)
}
selectSegment(segmentId: number) {
if (this.params.view.name === 'select') {
if (this.params.view.params.selectedSegment === segmentId) return;
const newParams = Params.copyValues(this.params);
if (newParams.view.name === 'select') {
newParams.view.params.selectedSegment = segmentId;
}
const state = this.plugin.state.data;
const update = state.build().to(this.ref).update(newParams);
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
}
async update(params: Params.Values) {
const oldParams = this.params;
this.params = params;
if (!this.metadata) {
const response = await fetch(this.getMetadataUrl());
const rawMetadata = await response.json();
this.metadata = new MetadataWrapper(rawMetadata);
}
if (!this.visuals) {
this.initVisualInfos();
} else if (!Params.detailsEqual(this.params, oldParams)) {
this.updateVisualInfoDetails();
}
switch (params.view.name) {
case 'off':
await this.disableVisuals();
break;
case 'select':
await this.enableVisuals(params.view.params.selectedSegment);
break;
case 'all':
await this.enableVisuals();
break;
default:
throw new Error('Not implemented');
}
if (params.view.name !== 'off' && !this.backgroundSegmentsInitialized) {
this.guessBackgroundSegments();
this.backgroundSegmentsInitialized = true;
}
if (params.view.name === 'select' && !this.focusSubscription) {
this.focusSubscription = this.subscribeObservable(this.focusObservable, data => { this.selectSegment(data?.segmentId ?? NO_SEGMENT); });
} else if (params.view.name !== 'select' && this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = undefined;
}
return true;
}
private getMetadataUrl() {
return `${this.parentData.serverUrl}/${this.parentData.source}/${this.parentData.entryId}/metadata`;
}
private getMeshUrl(segment: number, detail: number) {
return `${this.parentData.serverUrl}/${this.parentData.source}/${this.parentData.entryId}/mesh_bcif/${segment}/${detail}`;
}
private initVisualInfos() {
const visuals: { [tag: string]: VisualInfo } = {};
for (const segid of this.metadata!.meshSegmentIds) {
const name = this.metadata?.getSegment(segid)?.biological_annotation.name ?? DEFAULT_SEGMENT_NAME;
const color = this.metadata?.getSegmentColor(segid) ?? DEFAULT_SEGMENT_COLOR;
for (const detailType of VisualInfo.DetailTypes) {
const visual: VisualInfo = {
tag: VisualInfo.tagFor(segid, detailType),
segmentId: segid,
segmentName: name,
detailType: detailType,
detail: -1, // to be set at the end
color: color,
visible: false,
data: undefined,
};
visuals[visual.tag] = visual;
}
}
this.visuals = visuals;
this.updateVisualInfoDetails();
}
private updateVisualInfoDetails() {
let highDetail: number | undefined;
let lowDetail: number | undefined;
switch (this.params.view.name) {
case 'off':
lowDetail = undefined;
highDetail = undefined;
break;
case 'select':
lowDetail = this.params.view.params.baseDetail;
highDetail = this.params.view.params.focusDetail;
break;
case 'all':
lowDetail = this.params.view.params.detail;
highDetail = undefined;
break;
}
for (const tag in this.visuals) {
const visual = this.visuals[tag];
const preferredDetail = (visual.detailType === 'high') ? highDetail : lowDetail;
if (preferredDetail !== undefined) {
visual.detail = this.metadata!.getSufficientMeshDetail(visual.segmentId, preferredDetail);
}
}
}
private async enableVisuals(highDetailSegment?: number) {
for (const tag in this.visuals) {
const visual = this.visuals[tag];
const requiredDetailType = visual.segmentId === highDetailSegment ? 'high' : 'low';
if (visual.detailType === requiredDetailType) {
visual.data = await this.getMeshData(visual);
visual.visible = true;
} else {
visual.visible = false;
}
}
}
private async disableVisuals() {
for (const tag in this.visuals) {
const visual = this.visuals[tag];
visual.visible = false;
}
}
/** Fetch data in current `visual.detail`, or return already fetched data (if available in the correct detail). */
private async getMeshData(visual: VisualInfo): Promise<MeshlistData> {
if (visual.data && visual.data.detail === visual.detail) {
// Do not recreate
return visual.data;
}
// TODO cache
const url = this.getMeshUrl(visual.segmentId, visual.detail);
const urlAsset = Asset.getUrlAsset(this.plugin.managers.asset, url);
const asset = await this.plugin.runTask(this.plugin.managers.asset.resolve(urlAsset, 'binary'));
const parsed = await this.plugin.runTask(CIF.parseBinary(asset.data));
if (parsed.isError) {
throw new Error(`Failed parsing CIF file from ${url}`);
}
const meshlistData = await MeshlistData.fromCIF(parsed.result, visual.segmentId, visual.segmentName, visual.detail);
meshlistData.ownerId = this.id;
// const bbox = MeshlistData.bbox(meshlistData);
// const bboxVolume = bbox ? MS.Box3D.volume(bbox) : 0.0;
// console.log(`BBox ${visual.segmentId}: ${Math.round(bboxVolume! / 1e6)} M`, bbox); // DEBUG
return meshlistData;
}
private async guessBackgroundSegments() {
const bboxes: { [segid: number]: Box3D } = {};
for (const tag in this.visuals) {
const visual = this.visuals[tag];
if (visual.detailType === 'low' && visual.data) {
const bbox = MeshlistData.bbox(visual.data);
if (bbox) {
bboxes[visual.segmentId] = bbox;
}
}
}
const totalBbox = MeshlistData.combineBBoxes(Object.values(bboxes));
const totalVolume = totalBbox ? Box3D.volume(totalBbox) : 0.0;
// console.log(`BBox total: ${Math.round(totalVolume! / 1e6)} M`, totalBbox); // DEBUG
const isBgSegment: { [segid: number]: boolean } = {};
for (const segid in bboxes) {
const bbox = bboxes[segid];
const bboxVolume = Box3D.volume(bbox);
isBgSegment[segid] = (bboxVolume > totalVolume * BACKGROUND_SEGMENT_VOLUME_THRESHOLD);
// console.log(`BBox ${segid}: ${Math.round(bboxVolume! / 1e6)} M, ${bboxVolume / totalVolume}`, bbox); // DEBUG
}
this.backgroundSegments = isBgSegment;
}
getDescription() {
return Params.ViewTypeChoice.prettyName(this.params.view.name);
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Choice } from '../../volumes-and-segmentations/helpers';
export const DEFAULT_MESH_SERVER = 'http://localhost:9000/v2';
export class MeshServerInfo extends PluginStateObject.Create<MeshServerInfo.Data>({ name: 'Volume Server', typeClass: 'Object' }) { }
export namespace MeshServerInfo {
export const MeshSourceChoice = new Choice({ empiar: 'EMPIAR', emdb: 'EMDB' }, 'empiar');
export type MeshSource = Choice.Values<typeof MeshSourceChoice>;
export const Params = {
serverUrl: PD.Text(DEFAULT_MESH_SERVER),
source: MeshSourceChoice.PDSelect(),
entryId: PD.Text(''),
};
export type Data = PD.Values<typeof Params>;
}

View File

@@ -0,0 +1,214 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { PluginStateObject } from '../../../mol-plugin-state/objects';
import { PluginContext } from '../../../mol-plugin/context';
import { ShapeRepresentation } from '../../../mol-repr/shape/representation';
import { StateAction, StateTransformer } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { shallowEqualObjects } from '../../../mol-util';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { BACKGROUND_OPACITY, FOREROUND_OPACITY, MeshlistData, VolsegTransform } from '../mesh-extension';
import { MeshStreaming, NO_SEGMENT } from './behavior';
import { MeshServerInfo } from './server-info';
// // // // // // // // // // // // // // // // // // // // // // // //
export const MeshServerTransformer = VolsegTransform({
name: 'mesh-server-info',
from: PluginStateObject.Root,
to: MeshServerInfo,
params: MeshServerInfo.Params,
})({
apply({ a, params }, plugin: PluginContext) { // `a` is the parent node, `params` are 2nd argument to To.apply()
params.serverUrl = params.serverUrl.replace(/\/*$/, ''); // trim trailing slash
const description: string = params.entryId;
return new MeshServerInfo({ ...params }, { label: 'Mesh Server', description: description });
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
export const MeshStreamingTransformer = VolsegTransform({
name: 'mesh-streaming-from-server-info',
display: { name: 'Mesh Streaming' },
from: MeshServerInfo,
to: MeshStreaming,
params: a => MeshStreaming.Params.create(a!.data),
})({
canAutoUpdate() { return true; },
apply({ a, params }, plugin: PluginContext) {
return Task.create('Mesh Streaming', async ctx => {
const behavior = new MeshStreaming.Behavior(plugin, a.data, params);
await behavior.update(params);
return new MeshStreaming(behavior, { label: 'Mesh Streaming', description: behavior.getDescription() });
});
},
update({ a, b, oldParams, newParams }) {
return Task.create('Update Mesh Streaming', async ctx => {
if (a.data.source !== b.data.parentData.source || a.data.entryId !== b.data.parentData.entryId) {
return StateTransformer.UpdateResult.Recreate;
}
b.data.parentData = a.data;
await b.data.update(newParams);
b.description = b.data.getDescription();
return StateTransformer.UpdateResult.Updated;
});
}
});
// // // // // // // // // // // // // // // // // // // // // // // //
interface MeshVisualGroupData {
opacity: number,
}
// export type MeshVisualGroupTransformer = typeof MeshVisualGroupTransformer;
export const MeshVisualGroupTransformer = VolsegTransform({
name: 'mesh-visual-group-from-streaming',
display: { name: 'Mesh Visuals for a Segment' },
from: MeshStreaming,
to: PluginStateObject.Group,
params: {
/** Shown on the node in GUI */
label: PD.Text('', { isHidden: true }),
/** Shown on the node in GUI (gray letters) */
description: PD.Text(''),
segmentId: PD.Numeric(NO_SEGMENT, {}, { isHidden: true }),
opacity: PD.Numeric(-1, { min: 0, max: 1, step: 0.01 }),
}
})({
apply({ a, params }, plugin) {
trySetAutoOpacity(params, a);
return new PluginStateObject.Group({ opacity: params.opacity }, params);
},
update({ a, b, oldParams, newParams }, plugin) {
if (shallowEqualObjects(oldParams, newParams)) {
return StateTransformer.UpdateResult.Unchanged;
}
newParams.label ||= oldParams.label; // Protect against resetting params to invalid defaults
if (newParams.segmentId === NO_SEGMENT) newParams.segmentId = oldParams.segmentId; // Protect against resetting params to invalid defaults
trySetAutoOpacity(newParams, a);
b.label = newParams.label;
b.description = newParams.description;
(b.data as MeshVisualGroupData).opacity = newParams.opacity;
return StateTransformer.UpdateResult.Updated;
},
canAutoUpdate({ oldParams, newParams }, plugin) {
return newParams.description === oldParams.description;
},
});
function trySetAutoOpacity(params: StateTransformer.Params<typeof MeshVisualGroupTransformer>, parent: MeshStreaming) {
if (params.opacity === -1) {
const isBgSegment = parent.data.backgroundSegments[params.segmentId];
if (isBgSegment !== undefined) {
params.opacity = isBgSegment ? BACKGROUND_OPACITY : FOREROUND_OPACITY;
}
}
}
// // // // // // // // // // // // // // // // // // // // // // // //
export const MeshVisualTransformer = VolsegTransform({
name: 'mesh-visual-from-streaming',
display: { name: 'Mesh Visual from Streaming' },
from: MeshStreaming,
to: PluginStateObject.Shape.Representation3D,
params: {
/** Must be set to PluginStateObject reference to self */
ref: PD.Text('', { isHidden: true, isEssential: true }), // QUESTION what is isEssential
/** Identification of the mesh visual, e.g. 'low-2' */
tag: PD.Text('', { isHidden: true, isEssential: true }),
/** Opacity of the visual (not to be set directly, but controlled by the opacity of the parent Group, and by VisualInfo.visible) */
opacity: PD.Numeric(-1, { min: 0, max: 1, step: 0.01 }, { isHidden: true }),
}
})({
apply({ a, params, spine }, plugin: PluginContext) {
return Task.create('Mesh Visual', async ctx => {
const visualInfo: MeshStreaming.VisualInfo = a.data.visuals![params.tag];
if (!visualInfo) throw new Error(`VisualInfo with tag '${params.tag}' is missing.`);
const groupData = spine.getAncestorOfType(PluginStateObject.Group)?.data as MeshVisualGroupData | undefined;
params.opacity = visualInfo.visible ? (groupData?.opacity ?? FOREROUND_OPACITY) : 0.0;
const props = PD.getDefaultValues(Mesh.Params);
props.flatShaded = true; // `flatShaded: true` is to see the real mesh vertices and triangles (default: false)
props.alpha = params.opacity;
const repr = ShapeRepresentation((ctx, meshlist: MeshlistData) => MeshlistData.getShape(meshlist, visualInfo.color), Mesh.Utils);
await repr.createOrUpdate(props, visualInfo.data ?? MeshlistData.empty()).runInContext(ctx);
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: visualInfo.data }, { label: 'Mesh Visual', description: params.tag });
});
},
update({ a, b, oldParams, newParams, spine }, plugin: PluginContext) {
return Task.create('Update Mesh Visual', async ctx => {
newParams.ref ||= oldParams.ref; // Protect against resetting params to invalid defaults
newParams.tag ||= oldParams.tag; // Protect against resetting params to invalid defaults
const visualInfo: MeshStreaming.VisualInfo = a.data.visuals![newParams.tag];
if (!visualInfo) throw new Error(`VisualInfo with tag '${newParams.tag}' is missing.`);
const oldData = b.data.sourceData as MeshlistData | undefined;
if (visualInfo.data?.detail !== oldData?.detail) {
return StateTransformer.UpdateResult.Recreate;
}
const groupData = spine.getAncestorOfType(PluginStateObject.Group)?.data as MeshVisualGroupData | undefined;
const newOpacity = visualInfo.visible ? (groupData?.opacity ?? FOREROUND_OPACITY) : 0.0; // do not store to newParams directly, because oldParams and newParams might point to the same object!
if (newOpacity !== oldParams.opacity) {
newParams.opacity = newOpacity;
await b.data.repr.createOrUpdate({ alpha: newParams.opacity }).runInContext(ctx);
return StateTransformer.UpdateResult.Updated;
} else {
return StateTransformer.UpdateResult.Unchanged;
}
});
},
canAutoUpdate(params, globalCtx) {
return true;
},
dispose({ b, params }, plugin) {
b?.data.repr.destroy(); // QUESTION is this correct?
},
});
// // // // // // // // // // // // // // // // // // // // // // // //
export const InitMeshStreaming = StateAction.build({
display: { name: 'Mesh Streaming' },
from: PluginStateObject.Root,
params: MeshServerInfo.Params,
isApplicable: (a, _, plugin: PluginContext) => true
})(function (p, plugin: PluginContext) {
return Task.create('Mesh Streaming', async ctx => {
const { params } = p;
// p.ref
const serverNode = await plugin.build().to(p.ref).apply(MeshServerTransformer, params).commit();
// const serverNode = await plugin.build().toRoot().apply(MeshServerTransformer, params).commit();
const streamingNode = await plugin.build().to(serverNode).apply(MeshStreamingTransformer, {}).commit();
const visuals = streamingNode.data?.visuals ?? {};
const bgSegments = streamingNode.data?.backgroundSegments ?? {};
const segmentGroups: { [segid: number]: string } = {};
for (const tag in visuals) {
const segid = visuals[tag].segmentId;
if (!segmentGroups[segid]) {
let description = visuals[tag].segmentName;
if (bgSegments[segid]) description += ' (background)';
const group = await plugin.build().to(streamingNode).apply(MeshVisualGroupTransformer, { label: `Segment ${segid}`, description: description, segmentId: segid }, { state: { isCollapsed: true } }).commit();
segmentGroups[segid] = group.ref;
}
}
const visualsUpdate = plugin.build();
for (const tag in visuals) {
const ref = `${streamingNode.ref}-${tag}`;
const segid = visuals[tag].segmentId;
visualsUpdate.to(segmentGroups[segid]).apply(MeshVisualTransformer, { ref: ref, tag: tag }, { ref: ref }); // ref - hack to allow the node make itself invisible
}
await plugin.state.data.updateTree(visualsUpdate).runInContext(ctx); // QUESTION what is really the difference between this and `visualsUpdate.commit()`?
});
});
// TODO make available in GUI, in left panel or in right panel like Volume Streaming in src/mol-plugin-ui/structure/volume.tsx?

View File

@@ -0,0 +1,340 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
/** Helper functions for manipulation with mesh data. */
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { CIF, CifFile } from '../../mol-io/reader/cif';
import { Box3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { volumeFromDensityServerData } from '../../mol-model-formats/volume/density-server';
import { Grid } from '../../mol-model/volume';
import { ColorNames } from '../../mol-util/color/names';
import { TypedArray } from '../../mol-util/type-helpers';
import { CIF_schema_mesh } from './mesh-cif-schema';
type MeshModificationParams = {
scale?: [number, number, number],
shift?: [number, number, number],
matrix?: Mat4,
group?: number,
invertSides?: boolean
};
/** Modify mesh in-place */
export function modify(m: Mesh, params: MeshModificationParams) {
if (params.scale !== undefined) {
const [qx, qy, qz] = params.scale;
const vertices = m.vertexBuffer.ref.value;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i] *= qx;
vertices[i + 1] *= qy;
vertices[i + 2] *= qz;
}
}
if (params.shift !== undefined) {
const [dx, dy, dz] = params.shift;
const vertices = m.vertexBuffer.ref.value;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i] += dx;
vertices[i + 1] += dy;
vertices[i + 2] += dz;
}
}
if (params.matrix !== undefined) {
const r = m.vertexBuffer.ref.value;
const matrix = params.matrix;
const size = 3 * m.vertexCount;
for (let i = 0; i < size; i += 3) {
Vec3.transformMat4Offset(r, r, matrix, i, i, 0);
}
}
if (params.group !== undefined) {
const groups = m.groupBuffer.ref.value;
for (let i = 0; i < groups.length; i++) {
groups[i] = params.group;
}
}
if (params.invertSides) {
const indices = m.indexBuffer.ref.value;
let tmp;
for (let i = 0; i < indices.length; i += 3) {
tmp = indices[i];
indices[i] = indices[i + 1];
indices[i + 1] = tmp;
}
const normals = m.normalBuffer.ref.value;
for (let i = 0; i < normals.length; i++) {
normals[i] *= -1;
}
}
}
/** Create a copy a mesh, possibly modified */
export function copy(m: Mesh, modification?: MeshModificationParams): Mesh {
const nVertices = m.vertexCount;
const nTriangles = m.triangleCount;
const vertices = new Float32Array(m.vertexBuffer.ref.value);
const indices = new Uint32Array(m.indexBuffer.ref.value);
const normals = new Float32Array(m.normalBuffer.ref.value);
const groups = new Float32Array(m.groupBuffer.ref.value);
const result = Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
if (modification) {
modify(result, modification);
}
return result;
}
/** Join more meshes into one */
export function concat(...meshes: Mesh[]): Mesh {
const nVertices = sum(meshes.map(m => m.vertexCount));
const nTriangles = sum(meshes.map(m => m.triangleCount));
const vertices = concatArrays(Float32Array, meshes.map(m => m.vertexBuffer.ref.value));
const normals = concatArrays(Float32Array, meshes.map(m => m.normalBuffer.ref.value));
const groups = concatArrays(Float32Array, meshes.map(m => m.groupBuffer.ref.value));
const newIndices = [];
let offset = 0;
for (const m of meshes) {
newIndices.push(m.indexBuffer.ref.value.map(i => i + offset));
offset += m.vertexCount;
}
const indices = concatArrays(Uint32Array, newIndices);
return Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
}
/** Return Mesh from CIF data and mesh IDs (group IDs).
* Assume the CIF contains coords in grid space,
* transform the output mesh to `space` */
export async function meshFromCif(data: CifFile, invertSides: boolean | undefined = undefined, outSpace: 'grid' | 'fractional' | 'cartesian' = 'cartesian'): Promise<{ mesh: Mesh, meshIds: number[] }> {
const volumeInfoBlock = data.blocks.find(b => b.header === 'VOLUME_INFO');
const meshesBlock = data.blocks.find(b => b.header === 'MESHES');
if (!volumeInfoBlock || !meshesBlock) throw new Error('Missing VOLUME_INFO or MESHES block in mesh CIF file');
const volumeInfoCif = CIF.schema.densityServer(volumeInfoBlock);
const meshCif = CIF_schema_mesh(meshesBlock);
const nVertices = meshCif.mesh_vertex._rowCount;
const nTriangles = Math.floor(meshCif.mesh_triangle._rowCount / 3);
const mesh_id = meshCif.mesh.id.toArray();
const vertex_meshId = meshCif.mesh_vertex.mesh_id.toArray();
const x = meshCif.mesh_vertex.x.toArray();
const y = meshCif.mesh_vertex.y.toArray();
const z = meshCif.mesh_vertex.z.toArray();
const triangle_meshId = meshCif.mesh_triangle.mesh_id.toArray();
const triangle_vertexId = meshCif.mesh_triangle.vertex_id.toArray();
// Shift indices from within-mesh indices to overall indices
const indices = new Uint32Array(3 * nTriangles);
const offsets = offsetMap(vertex_meshId);
for (let i = 0; i < 3 * nTriangles; i++) {
const offset = offsets.get(triangle_meshId[i])!;
indices[i] = offset + triangle_vertexId[i];
}
const vertices = flattenCoords(x, y, z);
const normals = new Float32Array(3 * nVertices);
const groups = new Float32Array(vertex_meshId);
const mesh = Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
invertSides ??= isInverted(mesh);
if (invertSides) {
modify(mesh, { invertSides: true }); // Vertex orientation convention is opposite in Volseg API and in MolStar
}
if (outSpace === 'cartesian') {
const volume = await volumeFromDensityServerData(volumeInfoCif).run();
const gridToCartesian = Grid.getGridToCartesianTransform(volume.grid);
modify(mesh, { matrix: gridToCartesian });
} else if (outSpace === 'fractional') {
const gridSize = volumeInfoCif.volume_data_3d_info.sample_count.value(0);
const originFract = volumeInfoCif.volume_data_3d_info.origin.value(0);
const dimensionFract = volumeInfoCif.volume_data_3d_info.dimensions.value(0);
if (dimensionFract[0] !== 1 || dimensionFract[1] !== 1 || dimensionFract[2] !== 1) throw new Error(`Asserted the fractional dimensions are [1,1,1], but are actually [${dimensionFract}]`);
const scale: [number, number, number] = [1 / gridSize[0], 1 / gridSize[1], 1 / gridSize[2]];
modify(mesh, { scale: scale, shift: Array.from(originFract) as any });
}
Mesh.computeNormals(mesh); // normals only necessary if flatShaded==false
// const boxMesh = makeMeshFromBox([[0,0,0], [1,1,1]], 1);
// const gridSize = volumeInfoCif.volume_data_3d_info.sample_count.value(0); const boxMesh = makeMeshFromBox([[0,0,0], Array.from(gridSize)] as any, 1);
// const cellSize = volumeInfoCif.volume_data_3d_info.spacegroup_cell_size.value(0); const boxMesh = makeMeshFromBox([[0, 0, 0], Array.from(cellSize)] as any, 1);
// mesh = concat(mesh, boxMesh); // debug
return { mesh: mesh, meshIds: Array.from(mesh_id) };
}
function isInverted(mesh: Mesh): boolean {
const vertices = mesh.vertexBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const center = meshCenter(mesh);
const center3 = Vec3.create(3 * center[0], 3 * center[1], 3 * center[2]);
let dirMetric = 0.0;
const [a, b, c, u, v, normal, radius] = [Vec3(), Vec3(), Vec3(), Vec3(), Vec3(), Vec3(), Vec3()];
for (let i = 0; i < indices.length; i += 3) {
Vec3.fromArray(a, vertices, 3 * indices[i]);
Vec3.fromArray(b, vertices, 3 * indices[i + 1]);
Vec3.fromArray(c, vertices, 3 * indices[i + 2]);
Vec3.sub(u, b, a);
Vec3.sub(v, c, b);
Vec3.cross(normal, u, v); // direction of the surface
Vec3.add(radius, a, b);
Vec3.add(radius, radius, c);
Vec3.sub(radius, radius, center3); // direction center -> this triangle
dirMetric += Vec3.dot(radius, normal);
}
return dirMetric < 0;
}
function meshCenter(mesh: Mesh) {
const vertices = mesh.vertexBuffer.ref.value;
const n = vertices.length;
let x = 0.0;
let y = 0.0;
let z = 0.0;
for (let i = 0; i < vertices.length; i += 3) {
x += vertices[i];
y += vertices[i + 1];
z += vertices[i + 2];
}
return Vec3.create(x / n, y / n, z / n);
}
function flattenCoords(x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number>): Float32Array {
const n = x.length;
const out = new Float32Array(3 * n);
for (let i = 0; i < n; i++) {
out[3 * i] = x[i];
out[3 * i + 1] = y[i];
out[3 * i + 2] = z[i];
}
return out;
}
/** Get mapping of unique values to the position of their first occurrence */
function offsetMap(values: ArrayLike<number>) {
const result = new Map<number, number>();
for (let i = 0; i < values.length; i++) {
if (!result.has(values[i])) {
result.set(values[i], i);
}
}
return result;
}
/** Return bounding box */
export function bbox(mesh: Mesh): Box3D | null { // Is there no function for this?
const nVertices = mesh.vertexCount;
const coords = mesh.vertexBuffer.ref.value;
if (nVertices === 0) {
return null;
}
let minX = coords[0], minY = coords[1], minZ = coords[2];
let maxX = minX, maxY = minY, maxZ = minZ;
for (let i = 0; i < 3 * nVertices; i += 3) {
const x = coords[i], y = coords[i + 1], z = coords[i + 2];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (z < minZ) minZ = z;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
if (z > maxZ) maxZ = z;
}
return Box3D.create(Vec3.create(minX, minY, minZ), Vec3.create(maxX, maxY, maxZ));
}
/** Example mesh - 1 triangle */
export function fakeFakeMesh1(): Mesh {
const nVertices = 3;
const nTriangles = 1;
const vertices = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
const indices = new Uint32Array([0, 1, 2]);
const normals = new Float32Array([0, 0, 1]);
const groups = new Float32Array([0]);
return Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
}
/** Example mesh - irregular tetrahedron */
export function fakeMesh4(): Mesh {
const nVertices = 4;
const nTriangles = 4;
const vertices = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]);
const indices = new Uint32Array([0, 2, 1, 0, 1, 3, 1, 2, 3, 2, 0, 3]);
const normals = new Float32Array([-1, -1, -1, 1, 0, 0, 0, 1, 0, 0, 0, 1]);
const groups = new Float32Array([0, 1, 2, 3]);
return Mesh.create(vertices, indices, normals, groups, nVertices, nTriangles);
}
/** Return a box-shaped mesh */
export function meshFromBox(box: [[number, number, number], [number, number, number]], group: number = 0) {
const [[x0, y0, z0], [x1, y1, z1]] = box;
const vertices = new Float32Array([
x0, y0, z0,
x1, y0, z0,
x0, y1, z0,
x1, y1, z0,
x0, y0, z1,
x1, y0, z1,
x0, y1, z1,
x1, y1, z1,
]);
const indices = new Uint32Array([
2, 1, 0, 1, 2, 3,
1, 4, 0, 4, 1, 5,
3, 5, 1, 5, 3, 7,
2, 7, 3, 7, 2, 6,
0, 6, 2, 6, 0, 4,
4, 7, 6, 7, 4, 5,
]);
const groups = new Float32Array([group, group, group, group, group, group, group, group]);
const normals = new Float32Array(8);
const mesh = Mesh.create(vertices, indices, normals, groups, 8, 12);
Mesh.computeNormals(mesh); // normals only necessary if flatShaded==false
return mesh;
}
function sum(array: number[]): number {
return array.reduce((a, b) => a + b, 0);
}
function concatArrays<T extends TypedArray>(t: new (len: number) => T, arrays: T[]): T {
const totalLength = arrays.map(a => a.length).reduce((a, b) => a + b, 0);
const result: T = new t(totalLength);
let offset = 0;
for (const array of arrays) {
result.set(array, offset);
offset += array.length;
}
return result;
}
/** Generate random colors (in a cycle) */
export const ColorGenerator = function* () {
const colors = shuffleArray(Object.values(ColorNames));
let i = 0;
while (true) {
yield colors[i];
i++;
if (i >= colors.length) i = 0;
}
}();
function shuffleArray<T>(array: T[]): T[] {
// Stealed from https://www.w3docs.com/snippets/javascript/how-to-randomize-shuffle-a-javascript-array.html
let curId = array.length;
// There remain elements to shuffle
while (0 !== curId) {
// Pick a remaining element
const randId = Math.floor(Math.random() * curId);
curId -= 1;
// Swap it with the current element.
const tmp = array[curId];
array[curId] = array[randId];
array[randId] = tmp;
}
return array;
}

View File

@@ -2,6 +2,7 @@
* Copyright (c) 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>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -14,6 +15,7 @@ import { CustomPropSymbol } from '../../../mol-script/language/symbol';
import { Type } from '../../../mol-script/language/type';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { AtomicIndex } from '../../../mol-model/structure/model/properties/atomic';
export { QualityAssessment };
@@ -71,14 +73,28 @@ namespace QualityAssessment {
localNames.set(ma_qa_metric.id.value(i), name);
}
const residueKey: AtomicIndex.ResidueLabelKey = {
label_entity_id: '',
label_asym_id: '',
label_seq_id: 0,
pdbx_PDB_ins_code: undefined,
};
for (let i = 0, il = ma_qa_metric_local._rowCount; i < il; i++) {
if (model_id.value(i) !== model.modelNum) continue;
const labelAsymId = label_asym_id.value(i);
const entityIndex = index.findEntity(labelAsymId);
const rI = index.findResidue(model.entities.data.id.value(entityIndex), labelAsymId, label_seq_id.value(i));
const name = localNames.get(metric_id.value(i))!;
localMetrics.get(name)!.set(rI, metric_value.value(i));
residueKey.label_entity_id = model.entities.data.id.value(entityIndex);
residueKey.label_asym_id = labelAsymId;
residueKey.label_seq_id = label_seq_id.value(i);
const rI = index.findResidueLabel(residueKey);
if (rI >= 0) {
const name = localNames.get(metric_id.value(i))!;
localMetrics.get(name)!.set(rI, metric_value.value(i));
}
}
return {

View File

@@ -29,7 +29,7 @@ export { StructureQualityReport };
type StructureQualityReport = PropertyWrapper<{
issues: IndexedCustomProperty.Residue<string[]>,
issueTypes: string[]
}| undefined>
} | undefined>
namespace StructureQualityReport {
export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/';

View File

@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 2022-08-20T16:36:05-07:00
// Generated on 2023-01-15T10:00:07-08:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -343,6 +343,14 @@ export type Citation = {
*
*/
readonly journal_abbrev?: Maybe<Scalars['String']>;
/**
* Full name of the cited journal; relevant for journal articles.
*
* Examples:
* Journal of Molecular Biology
*
*/
readonly journal_full?: Maybe<Scalars['String']>;
/**
* The American Society for Testing and Materials (ASTM) code
* assigned to the journal cited (also referred to as the CODEN
@@ -550,6 +558,7 @@ export type CoreBranchedEntityInstance = {
readonly rcsb_id: Scalars['String'];
readonly rcsb_latest_revision?: Maybe<RcsbLatestRevision>;
readonly rcsb_ligand_neighbors?: Maybe<ReadonlyArray<Maybe<RcsbLigandNeighbors>>>;
readonly struct_asym?: Maybe<StructAsym>;
};
export type CoreChemComp = {
@@ -661,6 +670,7 @@ export type CoreEntry = {
readonly exptl?: Maybe<ReadonlyArray<Maybe<Exptl>>>;
readonly exptl_crystal?: Maybe<ReadonlyArray<Maybe<ExptlCrystal>>>;
readonly exptl_crystal_grow?: Maybe<ReadonlyArray<Maybe<ExptlCrystalGrow>>>;
readonly ma_data?: Maybe<ReadonlyArray<Maybe<MaData>>>;
/** Get all non-polymer (non-solvent) entities for this PDB entry. */
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
readonly pdbx_SG_project?: Maybe<ReadonlyArray<Maybe<PdbxSgProject>>>;
@@ -701,6 +711,7 @@ export type CoreEntry = {
/** The list of content types associated with this entry. */
readonly rcsb_associated_holdings?: Maybe<CurrentEntry>;
readonly rcsb_binding_affinity?: Maybe<ReadonlyArray<Maybe<RcsbBindingAffinity>>>;
readonly rcsb_comp_model_provenance?: Maybe<RcsbCompModelProvenance>;
readonly rcsb_entry_container_identifiers: RcsbEntryContainerIdentifiers;
readonly rcsb_entry_group_membership?: Maybe<ReadonlyArray<Maybe<RcsbEntryGroupMembership>>>;
readonly rcsb_entry_info: RcsbEntryInfo;
@@ -713,6 +724,7 @@ export type CoreEntry = {
*
*/
readonly rcsb_id: Scalars['String'];
readonly rcsb_ma_qa_metric_global?: Maybe<ReadonlyArray<Maybe<RcsbMaQaMetricGlobal>>>;
readonly rcsb_primary_citation?: Maybe<RcsbPrimaryCitation>;
readonly refine?: Maybe<ReadonlyArray<Maybe<Refine>>>;
readonly refine_analyze?: Maybe<ReadonlyArray<Maybe<RefineAnalyze>>>;
@@ -786,6 +798,7 @@ export type CoreNonpolymerEntityInstance = {
readonly rcsb_nonpolymer_instance_validation_score?: Maybe<ReadonlyArray<Maybe<RcsbNonpolymerInstanceValidationScore>>>;
readonly rcsb_nonpolymer_struct_conn?: Maybe<ReadonlyArray<Maybe<RcsbNonpolymerStructConn>>>;
readonly rcsb_target_neighbors?: Maybe<ReadonlyArray<Maybe<RcsbTargetNeighbors>>>;
readonly struct_asym?: Maybe<StructAsym>;
};
export type CorePfam = {
@@ -919,6 +932,7 @@ export type CorePolymerEntityInstance = {
readonly rcsb_polymer_instance_feature?: Maybe<ReadonlyArray<Maybe<RcsbPolymerInstanceFeature>>>;
readonly rcsb_polymer_instance_feature_summary?: Maybe<ReadonlyArray<Maybe<RcsbPolymerInstanceFeatureSummary>>>;
readonly rcsb_polymer_struct_conn?: Maybe<ReadonlyArray<Maybe<RcsbPolymerStructConn>>>;
readonly struct_asym?: Maybe<StructAsym>;
};
export type CorePubmed = {
@@ -1284,9 +1298,9 @@ export type Em2dCrystalEntity = {
readonly id: Scalars['String'];
/** pointer to _em_image_processing.id in the EM_IMAGE_PROCESSING category. */
readonly image_processing_id: Scalars['String'];
/** Unit-cell length a in Angstroms. */
/** Unit-cell length a in angstroms. */
readonly length_a?: Maybe<Scalars['Float']>;
/** Unit-cell length b in Angstroms. */
/** Unit-cell length b in angstroms. */
readonly length_b?: Maybe<Scalars['Float']>;
/** Thickness of 2D crystal */
readonly length_c?: Maybe<Scalars['Float']>;
@@ -1317,11 +1331,11 @@ export type Em3dCrystalEntity = {
readonly id: Scalars['String'];
/** pointer to _em_image_processing.id in the EM_IMAGE_PROCESSING category. */
readonly image_processing_id: Scalars['String'];
/** Unit-cell length a in Angstroms. */
/** Unit-cell length a in angstroms. */
readonly length_a?: Maybe<Scalars['Float']>;
/** Unit-cell length b in Angstroms. */
/** Unit-cell length b in angstroms. */
readonly length_b?: Maybe<Scalars['Float']>;
/** Unit-cell length c in Angstroms. */
/** Unit-cell length c in angstroms. */
readonly length_c?: Maybe<Scalars['Float']>;
/**
* Space group name.
@@ -1482,7 +1496,7 @@ export type Em3dReconstruction = {
*/
readonly refinement_type?: Maybe<Scalars['String']>;
/**
* The final resolution (in Angstroms)of the 3D reconstruction.
* The final resolution (in angstroms)of the 3D reconstruction.
*
* Examples:
* null, null
@@ -1558,7 +1572,7 @@ export type EmDiffractionShell = {
*/
readonly fourier_space_coverage?: Maybe<Scalars['Float']>;
/**
* High resolution limit for this shell (Angstroms)
* High resolution limit for this shell (angstroms)
*
* Examples:
* null
@@ -1568,7 +1582,7 @@ export type EmDiffractionShell = {
/** Unique identifier for the category em_diffraction_shell */
readonly id: Scalars['String'];
/**
* Low resolution limit for this shell (Angstroms)
* Low resolution limit for this shell (angstroms)
*
* Examples:
* null
@@ -1614,7 +1628,7 @@ export type EmDiffractionStats = {
*/
readonly fourier_space_coverage?: Maybe<Scalars['Float']>;
/**
* High resolution limit of the structure factor data, in Angstroms
* High resolution limit of the structure factor data, in angstroms
*
* Examples:
* null
@@ -1998,12 +2012,12 @@ export type EmImaging = {
/** The magnification indicated by the microscope readout. */
readonly nominal_magnification?: Maybe<Scalars['Int']>;
/**
* The specimen temperature maximum (degrees Kelvin) for the duration
* The specimen temperature maximum (kelvin) for the duration
* of imaging.
*/
readonly recording_temperature_maximum?: Maybe<Scalars['Float']>;
/**
* The specimen temperature minimum (degrees Kelvin) for the duration
* The specimen temperature minimum (kelvin) for the duration
* of imaging.
*/
readonly recording_temperature_minimum?: Maybe<Scalars['Float']>;
@@ -2028,7 +2042,7 @@ export type EmImaging = {
/** Foreign key to the EM_SPECIMEN category */
readonly specimen_id?: Maybe<Scalars['String']>;
/**
* The mean specimen stage temperature (degrees Kelvin) during imaging
* The mean specimen stage temperature (in kelvin) during imaging
* in the microscope.
*/
readonly temperature?: Maybe<Scalars['Float']>;
@@ -2215,7 +2229,7 @@ export type EmStaining = {
};
export type EmVitrification = {
/** The temperature (in degrees Kelvin) of the sample just prior to vitrification. */
/** The temperature (in kelvin) of the sample just prior to vitrification. */
readonly chamber_temperature?: Maybe<Scalars['Float']>;
/**
* This is the name of the cryogen.
@@ -2259,7 +2273,7 @@ export type EmVitrification = {
/** This data item is a pointer to _em_specimen.id */
readonly specimen_id: Scalars['String'];
/**
* The vitrification temperature (in degrees Kelvin), e.g.,
* The vitrification temperature (in kelvin), e.g.,
* temperature of the plunge instrument cryogen bath.
*/
readonly temp?: Maybe<Scalars['Float']>;
@@ -2364,6 +2378,14 @@ export type EntityPoly = {
*
*/
readonly pdbx_seq_one_letter_code_can?: Maybe<Scalars['String']>;
/**
* Evidence for the assignment of the polymer sequence.
*
* Allowable values:
* depositor provided, derived from coordinates
*
*/
readonly pdbx_sequence_evidence_code?: Maybe<Scalars['String']>;
/**
* The PDB strand/chain id(s) corresponding to this polymer entity.
*
@@ -3005,6 +3027,8 @@ export type Entry = {
* identifier.
*/
readonly id: Scalars['String'];
/** An identifier for the model collection associated with the entry. */
readonly ma_collection_id?: Maybe<Scalars['String']>;
};
export type Exptl = {
@@ -3263,6 +3287,29 @@ export type InterfacePartnerFeatureFeaturePositions = {
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
};
export type MaData = {
/**
* The type of data held in the dataset.
*
* Allowable values:
* coevolution MSA, input structure, model coordinates, other, polymeric template library, spatial restraints, target, target-template alignment, template structure
*
*/
readonly content_type?: Maybe<Scalars['String']>;
/** Details for other content types. */
readonly content_type_other_details?: Maybe<Scalars['String']>;
/** A unique identifier for the data. */
readonly id: Scalars['Int'];
/**
* An author-given name for the content held in the dataset.
*
* Examples:
* NMR NOE Distances, Target Template Alignment, Coevolution Data
*
*/
readonly name?: Maybe<Scalars['String']>;
};
export type MethodDetails = {
/** A description of special aspects of the clustering process */
readonly description?: Maybe<Scalars['String']>;
@@ -4199,7 +4246,7 @@ export type PdbxNmrExptlSampleConditions = {
*/
readonly pressure_units?: Maybe<Scalars['String']>;
/**
* The temperature (in Kelvin) at which NMR data were
* The temperature (in kelvin) at which NMR data were
* collected.
*/
readonly temperature?: Maybe<Scalars['String']>;
@@ -4453,7 +4500,7 @@ export type PdbxPrdAudit = {
* An identifier for the wwPDB site creating or modifying the molecule.
*
* Allowable values:
* BMRB, PDBC, PDBJ, PDBe, RCSB
* BMRB, PDBC, PDBE, PDBJ, RCSB
*
*/
readonly processing_site?: Maybe<Scalars['String']>;
@@ -6817,7 +6864,7 @@ export type Query = {
readonly assemblies?: Maybe<ReadonlyArray<Maybe<CoreAssembly>>>;
/** Get an assembly given the PDB ID and ASSEMBLY ID. Here ASSEMBLY ID is '1', '2', '3', etc. or 'deposited' for deposited coordinates. */
readonly assembly?: Maybe<CoreAssembly>;
/** Get a list of PDB branched entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
/** Get a list of PDB branched entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. Note that the ENTRY ID part must be upper case. */
readonly branched_entities?: Maybe<ReadonlyArray<Maybe<CoreBranchedEntity>>>;
/** Get a PDB branched entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
readonly branched_entity?: Maybe<CoreBranchedEntity>;
@@ -6841,9 +6888,9 @@ export type Query = {
readonly group_provenance?: Maybe<GroupProvenance>;
/** Get a pairwise polymeric interface given the PDB ID, ASSEMBLY ID and INTERFACE ID. */
readonly interface?: Maybe<CoreInterface>;
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. */
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. Note that the ENTRY ID part must be upper case. */
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. Note that the ENTRY ID part must be upper case. */
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
/** Get a PDB non-polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
readonly nonpolymer_entity?: Maybe<CoreNonpolymerEntity>;
@@ -6851,7 +6898,7 @@ export type Query = {
readonly nonpolymer_entity_instance?: Maybe<CoreNonpolymerEntityInstance>;
/** Get a list of PDB non-polymer entity instances (chains), given the list of ENTITY INSTANCE IDs. Here ENTITY INSTANCE ID identifies structural element in the asymmetric unit, e.g. 'A', 'B', etc. */
readonly nonpolymer_entity_instances?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntityInstance>>>;
/** Get a list of PDB polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
/** Get a list of PDB polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. Note that the ENTRY ID part must be upper case. */
readonly polymer_entities?: Maybe<ReadonlyArray<Maybe<CorePolymerEntity>>>;
/** Get a PDB polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
readonly polymer_entity?: Maybe<CorePolymerEntity>;
@@ -8421,6 +8468,31 @@ export type RcsbClusterMembership = {
readonly identity?: Maybe<Scalars['Int']>;
};
export type RcsbCompModelProvenance = {
/**
* Entry identifier corresponding to the computed structure model.
*
* Examples:
* AF-P60325-F1, ma-bak-cepc-0019
*
*/
readonly entry_id: Scalars['String'];
/**
* Source database for the computed structure model.
*
* Allowable values:
* AlphaFoldDB, ModelArchive
*
*/
readonly source_db?: Maybe<Scalars['String']>;
/** Source filename for the computed structure model. */
readonly source_filename?: Maybe<Scalars['String']>;
/** Source URL for computed structure model predicted aligned error (PAE) json file. */
readonly source_pae_url?: Maybe<Scalars['String']>;
/** Source URL for computed structure model file. */
readonly source_url?: Maybe<Scalars['String']>;
};
export type RcsbEntityHostOrganism = {
/**
* The beginning polymer sequence position for the polymer section corresponding
@@ -8657,7 +8729,7 @@ export type RcsbEntitySourceOrganism = {
* A code indicating the provenance of the source organism details for the entity
*
* Allowable values:
* PDB Primary Data
* PDB Primary Data, UniProt
*
*/
readonly provenance_source?: Maybe<Scalars['String']>;
@@ -8731,7 +8803,7 @@ export type RcsbEntryContainerIdentifiers = {
* Entry identifier for the container.
*
* Examples:
* 1KIP, 4HHB
* 4HHB, AF_AFP60325F1, MA_MABAKCEPC0019
*
*/
readonly entry_id: Scalars['String'];
@@ -8879,6 +8951,15 @@ export type RcsbEntryInfo = {
*
*/
readonly na_polymer_entity_types?: Maybe<Scalars['String']>;
/**
* This data item identifies secondary structure
* features of nucleic acids in the entry.
*
* Allowable values:
* a-form double helix, b-form double helix, bulge loop, double helix, four-way junction, hairpin loop, internal loop, mismatched base pair, other right-handed double helix, parallel strands, quadruple helix, tetraloop, three-way junction, triple helix, two-way junction, z-form double helix
*
*/
readonly ndb_struct_conf_na_feature_combined?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
/** Bound nonpolymer components in this entry. */
readonly nonpolymer_bound_components?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
/** The number of distinct non-polymer entities in the structure entry exclusive of solvent. */
@@ -8955,6 +9036,21 @@ export type RcsbEntryInfo = {
readonly software_programs_combined?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
/** The number of distinct solvent entities per deposited structure model. */
readonly solvent_entity_count?: Maybe<Scalars['Int']>;
/**
* Indicates if the structure was determined using experimental or computational methods.
*
* Allowable values:
* computational, experimental
*
*/
readonly structure_determination_methodology: Scalars['String'];
/**
* Indicates the priority of the value in _rcsb_entry_info.structure_determination_methodology.
* The lower the number the higher the priority.
* Priority values for "experimental" structures is currently set to 10 and
* the values for "computational" structures is set to 100.
*/
readonly structure_determination_methodology_priority?: Maybe<Scalars['Int']>;
};
export type RcsbEntryInfoDiffrnResolutionHigh = {
@@ -9294,6 +9390,49 @@ export type RcsbLigandNeighbors = {
readonly seq_id?: Maybe<Scalars['Int']>;
};
export type RcsbMaQaMetricGlobal = {
readonly ma_qa_metric_global?: Maybe<ReadonlyArray<Maybe<RcsbMaQaMetricGlobalMaQaMetricGlobal>>>;
/** The model identifier. */
readonly model_id: Scalars['Int'];
};
export type RcsbMaQaMetricGlobalMaQaMetricGlobal = {
/**
* Description of the global QA metric.
*
* Examples:
* confidence score predicting accuracy according to the CA-only Local Distance Difference Test (lDDT-CA) in [0,100]
*
*/
readonly description?: Maybe<Scalars['String']>;
/**
* Name of the global QA metric.
*
* Examples:
* pLDDT
*
*/
readonly name: Scalars['String'];
/**
* The type of global QA metric.
*
* Allowable values:
* PAE, contact probability, distance, energy, ipTM, normalized score, other, pLDDT, pLDDT all-atom, pLDDT all-atom in [0,1], pLDDT in [0,1], pTM, zscore
*
*/
readonly type: Scalars['String'];
/** Details for other type of global QA metric. */
readonly type_other_details?: Maybe<Scalars['String']>;
/**
* Value of the global QA metric.
*
* Examples:
* null
*
*/
readonly value: Scalars['Float'];
};
export type RcsbMembraneLineage = {
/** Hierarchy depth. */
readonly depth?: Maybe<Scalars['Int']>;
@@ -10332,7 +10471,7 @@ export type RcsbPolymerEntityContainerIdentifiersReferenceSequenceIdentifiers =
* Source of the reference database assignment
*
* Allowable values:
* PDB, RCSB, SIFTS
* PDB, RCSB, SIFTS, UniProt
*
*/
readonly provenance_source?: Maybe<Scalars['String']>;
@@ -10737,7 +10876,7 @@ export type RcsbPolymerInstanceFeature = {
* A type or category of the feature.
*
* Allowable values:
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
* ANGLE_OUTLIER, BEND, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, HELX_LH_PP_P, HELX_RH_3T_P, HELX_RH_AL_P, HELX_RH_PI_P, MA_QA_METRIC_LOCAL_TYPE_CONTACT_PROBABILITY, MA_QA_METRIC_LOCAL_TYPE_DISTANCE, MA_QA_METRIC_LOCAL_TYPE_ENERGY, MA_QA_METRIC_LOCAL_TYPE_IPTM, MA_QA_METRIC_LOCAL_TYPE_NORMALIZED_SCORE, MA_QA_METRIC_LOCAL_TYPE_OTHER, MA_QA_METRIC_LOCAL_TYPE_PAE, MA_QA_METRIC_LOCAL_TYPE_PLDDT, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM_[0,1], MA_QA_METRIC_LOCAL_TYPE_PLDDT_[0,1], MA_QA_METRIC_LOCAL_TYPE_PTM, MA_QA_METRIC_LOCAL_TYPE_ZSCORE, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, STRN, TURN_TY1_P, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
*
*/
readonly type?: Maybe<Scalars['String']>;
@@ -10748,7 +10887,7 @@ export type RcsbPolymerInstanceFeatureAdditionalProperties = {
* The additional property name.
*
* Allowable values:
* CATH_DOMAIN_ID, CATH_NAME, ECOD_DOMAIN_ID, ECOD_FAMILY_NAME, OMEGA_ANGLE, PARTNER_ASYM_ID, PARTNER_BOND_DISTANCE, PARTNER_COMP_ID, SCOP2_DOMAIN_ID, SCOP2_FAMILY_ID, SCOP2_FAMILY_NAME, SCOP2_SUPERFAMILY_ID, SCOP2_SUPERFAMILY_NAME, SCOP_DOMAIN_ID, SCOP_NAME, SCOP_SUN_ID, SHEET_SENSE
* CATH_DOMAIN_ID, CATH_NAME, ECOD_DOMAIN_ID, ECOD_FAMILY_NAME, MODELCIF_MODEL_ID, OMEGA_ANGLE, PARTNER_ASYM_ID, PARTNER_BOND_DISTANCE, PARTNER_COMP_ID, SCOP2_DOMAIN_ID, SCOP2_FAMILY_ID, SCOP2_FAMILY_NAME, SCOP2_SUPERFAMILY_ID, SCOP2_SUPERFAMILY_NAME, SCOP_DOMAIN_ID, SCOP_NAME, SCOP_SUN_ID, SHEET_SENSE
*
*/
readonly name?: Maybe<Scalars['String']>;
@@ -10818,7 +10957,7 @@ export type RcsbPolymerInstanceFeatureSummary = {
* Type or category of the feature.
*
* Allowable values:
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SAbDab Antibody Heavy Chain Subclass, SAbDab Antibody Light Chain Subclass, SAbDab Antibody Light Chain Type, SCOP, SCOP2 Family, SCOP2 Superfamily, SCOP2B Superfamily, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
* ANGLE_OUTLIER, BEND, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, HELX_LH_PP_P, HELX_RH_3T_P, HELX_RH_AL_P, HELX_RH_PI_P, MA_QA_METRIC_LOCAL_TYPE_CONTACT_PROBABILITY, MA_QA_METRIC_LOCAL_TYPE_DISTANCE, MA_QA_METRIC_LOCAL_TYPE_ENERGY, MA_QA_METRIC_LOCAL_TYPE_IPTM, MA_QA_METRIC_LOCAL_TYPE_NORMALIZED_SCORE, MA_QA_METRIC_LOCAL_TYPE_OTHER, MA_QA_METRIC_LOCAL_TYPE_PAE, MA_QA_METRIC_LOCAL_TYPE_PLDDT, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM, MA_QA_METRIC_LOCAL_TYPE_PLDDT_ALL-ATOM_[0,1], MA_QA_METRIC_LOCAL_TYPE_PLDDT_[0,1], MA_QA_METRIC_LOCAL_TYPE_PTM, MA_QA_METRIC_LOCAL_TYPE_ZSCORE, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, STRN, TURN_TY1_P, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
*
*/
readonly type?: Maybe<Scalars['String']>;
@@ -13377,6 +13516,11 @@ export type ReflnsShell = {
};
export type Software = {
/**
* This data item is a pointer to _citation.id in the CITATION
* category.
*/
readonly citation_id?: Maybe<Scalars['String']>;
/**
* The classification of the program according to its
* major function.
@@ -13527,6 +13671,37 @@ export type Struct = {
readonly title?: Maybe<Scalars['String']>;
};
export type StructAsym = {
/**
* This data item is a pointer to _atom_site.pdbx_PDB_strand_id the
* ATOM_SITE category.
*
* Examples:
* 1ABC
*
*/
readonly pdbx_PDB_id?: Maybe<Scalars['String']>;
/**
* This data item is a pointer to _atom_site.ndb_alias_strand_id the
* ATOM_SITE category.
*/
readonly pdbx_alt_id?: Maybe<Scalars['String']>;
/**
* This data item gives the order of the structural elements in the
* ATOM_SITE category.
*/
readonly pdbx_order?: Maybe<Scalars['Int']>;
/**
* This data item describes the general type of the structural elements
* in the ATOM_SITE category.
*
* Allowable values:
* ATOMN, ATOMP, ATOMS, HETAC, HETAD, HETAI, HETAIN, HETAS, HETIC
*
*/
readonly pdbx_type?: Maybe<Scalars['String']>;
};
export type StructKeywords = {
/**
* Terms characterizing the macromolecular structure.

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { PluginCommands } from '../../mol-plugin/commands';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { BACKGROUND_SEGMENT_VOLUME_THRESHOLD } from '../meshes/mesh-streaming/behavior';
import { createMeshFromUrl } from '../meshes/mesh-extension';
import { Segment } from './volseg-api/data';
import { VolsegEntryData } from './entry-root';
const DEFAULT_MESH_DETAIL: number | null = 5; // null means worst
export class VolsegMeshSegmentationData {
private entryData: VolsegEntryData;
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
async loadSegmentation() {
const hasMeshes = this.entryData.metadata.meshSegmentIds.length > 0;
if (hasMeshes) {
await this.showSegments(this.entryData.metadata.allSegmentIds);
}
}
updateOpacity(opacity: number) {
const visuals = this.entryData.findNodesByTags('mesh-segment-visual');
const update = this.entryData.newUpdate();
for (const visual of visuals) {
update.to(visual).update(ShapeRepresentation3D, p => { (p as any).alpha = opacity; });
}
return update.commit();
}
async highlightSegment(segment: Segment) {
const visuals = this.entryData.findNodesByTags('mesh-segment-visual', `segment-${segment.id}`);
for (const visual of visuals) {
await PluginCommands.Interactivity.Object.Highlight(this.entryData.plugin, { state: this.entryData.plugin.state.data, ref: visual.transform.ref });
}
}
async selectSegment(segment?: number) {
if (segment === undefined || segment < 0) return;
const visuals = this.entryData.findNodesByTags('mesh-segment-visual', `segment-${segment}`);
const reprNode: PluginStateObject.Shape.Representation3D | undefined = visuals[0]?.obj;
if (!reprNode) return;
const loci = reprNode.data.repr.getAllLoci()[0];
if (!loci) return;
this.entryData.plugin.managers.interactivity.lociSelects.select({ loci: loci, repr: reprNode.data.repr }, false);
}
/** Make visible the specified set of mesh segments */
async showSegments(segments: number[]) {
const segmentsToShow = new Set(segments);
const visuals = this.entryData.findNodesByTags('mesh-segment-visual');
for (const visual of visuals) {
const theTag = visual.obj?.tags?.find(tag => tag.startsWith('segment-'));
if (!theTag) continue;
const id = parseInt(theTag.split('-')[1]);
const visibility = segmentsToShow.has(id);
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, !visibility); // true means hide, ¯\_(ツ)_/¯
segmentsToShow.delete(id);
}
const segmentsToCreate = this.entryData.metadata.meshSegmentIds.filter(seg => segmentsToShow.has(seg));
if (segmentsToCreate.length === 0) return;
let group = this.entryData.findNodesByTags('mesh-segmentation-group')[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup, { label: 'Segmentation', description: 'Mesh' }, { tags: ['mesh-segmentation-group'], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const totalVolume = this.entryData.metadata.gridTotalVolume;
const awaiting = [];
for (const seg of segmentsToCreate) {
const segment = this.entryData.metadata.getSegment(seg);
if (!segment) continue;
const detail = this.entryData.metadata.getSufficientMeshDetail(seg, DEFAULT_MESH_DETAIL);
const color = segment.colour.length >= 3 ? Color.fromNormalizedArray(segment.colour, 0) : ColorNames.gray;
const url = this.entryData.api.meshUrl_Bcif(this.entryData.source, this.entryData.entryId, seg, detail);
const label = segment.biological_annotation.name ?? `Segment ${seg}`;
const meshPromise = createMeshFromUrl(this.entryData.plugin, url, seg, detail, true, color, group,
BACKGROUND_SEGMENT_VOLUME_THRESHOLD * totalVolume, `<b>${label}</b>`, this.entryData.ref);
awaiting.push(meshPromise);
}
for (const promise of awaiting) await promise;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { TrajectoryFromMmCif } from '../../mol-plugin-state/transforms/model';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { VolsegEntryData } from './entry-root';
export class VolsegModelData {
private entryData: VolsegEntryData;
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
private async loadPdb(pdbId: string, parent: StateObjectSelector | StateObjectRef) {
const url = `https://www.ebi.ac.uk/pdbe/entry-files/download/${pdbId}.bcif`;
const dataNode = await this.entryData.plugin.build().to(parent).apply(Download, { url: url, isBinary: true }, { tags: ['fitted-model-data', `pdbid-${pdbId}`] }).commit();
const cifNode = await this.entryData.plugin.build().to(dataNode).apply(ParseCif).commit();
const trajectoryNode = await this.entryData.plugin.build().to(cifNode).apply(TrajectoryFromMmCif).commit();
await this.entryData.plugin.builders.structure.hierarchy.applyPreset(trajectoryNode, 'default', { representationPreset: 'polymer-cartoon' });
return dataNode;
}
async showPdbs(pdbIds: string[]) {
const segmentsToShow = new Set(pdbIds);
const visuals = this.entryData.findNodesByTags('fitted-model-data');
for (const visual of visuals) {
const theTag = visual.obj?.tags?.find(tag => tag.startsWith('pdbid-'));
if (!theTag) continue;
const id = theTag.split('-')[1];
const visibility = segmentsToShow.has(id);
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, !visibility); // true means hide, ¯\_(ツ)_/¯
segmentsToShow.delete(id);
}
const segmentsToCreate = Array.from(segmentsToShow);
if (segmentsToCreate.length === 0) return;
let group = this.entryData.findNodesByTags('fitted-models-group')[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup, { label: 'Fitted Models' }, { tags: ['fitted-models-group'], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const awaiting = [];
for (const pdbId of segmentsToCreate) {
awaiting.push(this.loadPdb(pdbId, group));
}
for (const promise of awaiting) await promise;
}
}

View File

@@ -0,0 +1,377 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { BehaviorSubject, distinctUntilChanged, Subject, throttleTime } from 'rxjs';
import { VolsegVolumeServerConfig } from '.';
import { Loci } from '../../mol-model/loci';
import { ShapeGroup } from '../../mol-model/shape';
import { Volume } from '../../mol-model/volume';
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
import { shallowEqualObjects } from '../../mol-util';
import { ParamDefinition } from '../../mol-util/param-definition';
import { MeshlistData } from '../meshes/mesh-extension';
import { DEFAULT_VOLSEG_SERVER, VolumeApiV2 } from './volseg-api/api';
import { Segment } from './volseg-api/data';
import { MetadataWrapper } from './volseg-api/utils';
import { VolsegMeshSegmentationData } from './entry-meshes';
import { VolsegModelData } from './entry-models';
import { VolsegLatticeSegmentationData } from './entry-segmentation';
import { VolsegState, VolsegStateData, VolsegStateParams } from './entry-state';
import { VolsegVolumeData, SimpleVolumeParamValues, VOLUME_VISUAL_TAG } from './entry-volume';
import * as ExternalAPIs from './external-api';
import { VolsegGlobalStateData } from './global-state';
import { applyEllipsis, Choice, isDefined, lazyGetter, splitEntryId } from './helpers';
import { type VolsegStateFromEntry } from './transformers';
import { StateTransforms } from '../../mol-plugin-state/transforms';
export const MAX_VOXELS = 10 ** 7;
// export const MAX_VOXELS = 10 ** 2; // DEBUG
export const BOX: [[number, number, number], [number, number, number]] | null = null;
// export const BOX: [[number, number, number], [number, number, number]] | null = [[-90, -90, -90], [90, 90, 90]]; // DEBUG
const MAX_ANNOTATIONS_IN_LABEL = 6;
const SourceChoice = new Choice({ emdb: 'EMDB', empiar: 'EMPIAR', idr: 'IDR' }, 'emdb');
export type Source = Choice.Values<typeof SourceChoice>;
export function createLoadVolsegParams(plugin?: PluginContext, entrylists: { [source: string]: string[] } = {}) {
const defaultVolumeServer = plugin?.config.get(VolsegVolumeServerConfig.DefaultServer) ?? DEFAULT_VOLSEG_SERVER;
return {
serverUrl: ParamDefinition.Text(defaultVolumeServer),
source: ParamDefinition.Mapped(SourceChoice.values[0], SourceChoice.options, src => entryParam(entrylists[src])),
};
}
function entryParam(entries: string[] = []) {
const options: [string, string][] = entries.map(e => [e, e]);
options.push(['__custom__', 'Custom']);
return ParamDefinition.Group({
entryId: ParamDefinition.Select(options[0][0], options, { description: 'Choose an entry from the list, or choose "Custom" and type any entry ID (useful when using other than default server).' }),
customEntryId: ParamDefinition.Text('', { hideIf: p => p.entryId !== '__custom__', description: 'Entry identifier, including the source prefix, e.g. "emd-1832"' }),
}, { isFlat: true });
}
type LoadVolsegParamValues = ParamDefinition.Values<ReturnType<typeof createLoadVolsegParams>>;
export function createVolsegEntryParams(plugin?: PluginContext) {
const defaultVolumeServer = plugin?.config.get(VolsegVolumeServerConfig.DefaultServer) ?? DEFAULT_VOLSEG_SERVER;
return {
serverUrl: ParamDefinition.Text(defaultVolumeServer),
source: SourceChoice.PDSelect(),
entryId: ParamDefinition.Text('emd-1832', { description: 'Entry identifier, including the source prefix, e.g. "emd-1832"' }),
};
}
type VolsegEntryParamValues = ParamDefinition.Values<ReturnType<typeof createVolsegEntryParams>>;
export namespace VolsegEntryParamValues {
export function fromLoadVolsegParamValues(params: LoadVolsegParamValues): VolsegEntryParamValues {
let entryId = (params.source.params as any).entryId;
if (entryId === '__custom__') {
entryId = (params.source.params as any).customEntryId;
}
return {
serverUrl: params.serverUrl,
source: params.source.name as Source,
entryId: entryId
};
}
}
export class VolsegEntry extends PluginStateObject.CreateBehavior<VolsegEntryData>({ name: 'Vol & Seg Entry' }) { }
type VolRepr3DT = typeof StateTransforms.Representation.VolumeRepresentation3D
export class VolsegEntryData extends PluginBehavior.WithSubscribers<VolsegEntryParamValues> {
plugin: PluginContext;
ref: string = '';
api: VolumeApiV2;
source: Source;
/** Number part of entry ID; e.g. '1832' */
entryNumber: string;
/** Full entry ID; e.g. 'emd-1832' */
entryId: string;
metadata: MetadataWrapper;
pdbs: string[];
public readonly volumeData = new VolsegVolumeData(this);
private readonly latticeSegmentationData = new VolsegLatticeSegmentationData(this);
private readonly meshSegmentationData = new VolsegMeshSegmentationData(this);
private readonly modelData = new VolsegModelData(this);
private highlightRequest = new Subject<Segment | undefined>();
private getStateNode = lazyGetter(() => this.plugin.state.data.selectQ(q => q.byRef(this.ref).subtree().ofType(VolsegState))[0] as StateObjectCell<VolsegState, StateTransform<typeof VolsegStateFromEntry>>, 'Missing VolsegState node. Must first create VolsegState for this VolsegEntry.');
public currentState = new BehaviorSubject(ParamDefinition.getDefaultValues(VolsegStateParams));
public currentVolume = new BehaviorSubject<StateTransform<VolRepr3DT> | undefined>(undefined);
private constructor(plugin: PluginContext, params: VolsegEntryParamValues) {
super(plugin, params);
this.plugin = plugin;
this.api = new VolumeApiV2(params.serverUrl);
this.source = params.source;
this.entryId = params.entryId;
this.entryNumber = splitEntryId(this.entryId).entryNumber;
}
private async initialize() {
const metadata = await this.api.getMetadata(this.source, this.entryId);
this.metadata = new MetadataWrapper(metadata);
this.pdbs = await ExternalAPIs.getPdbIdsForEmdbEntry(this.metadata.raw.grid.general.source_db_id ?? this.entryId);
// TODO use Asset?
}
static async create(plugin: PluginContext, params: VolsegEntryParamValues) {
const result = new VolsegEntryData(plugin, params);
await result.initialize();
return result;
}
async register(ref: string) {
this.ref = ref;
this.plugin.managers.lociLabels.addProvider(this.labelProvider);
try {
const params = this.getStateNode().obj?.data;
if (params) {
this.currentState.next(params);
}
} catch {
// do nothing
}
const volumeVisual = this.findNodesByTags(VOLUME_VISUAL_TAG)[0];
if (volumeVisual) this.currentVolume.next(volumeVisual.transform);
let volumeRef: string | undefined;
this.subscribeObservable(this.plugin.state.data.events.cell.stateUpdated, e => {
try { (this.getStateNode()); } catch { return; } // if state not does not exist yet
if (e.cell.transform.ref === this.getStateNode().transform.ref) {
const newState = this.getStateNode().obj?.data;
if (newState && !shallowEqualObjects(newState, this.currentState.value)) { // avoid repeated update
this.currentState.next(newState);
}
} else if (e.cell.transform.tags?.includes(VOLUME_VISUAL_TAG)) {
if (e.ref === volumeRef) {
this.currentVolume.next(e.cell.transform);
} else if (StateSelection.findAncestor(this.plugin.state.data.tree, this.plugin.state.data.cells, e.ref, a => a.transform.ref === ref)) {
volumeRef = e.ref;
this.currentVolume.next(e.cell.transform);
}
}
});
this.subscribeObservable(this.plugin.state.data.events.cell.removed, e => {
if (e.ref === volumeRef) {
volumeRef = undefined;
this.currentVolume.next(undefined);
}
});
this.subscribeObservable(this.plugin.behaviors.interaction.click, async e => {
const loci = e.current.loci;
const clickedSegment = this.getSegmentIdFromLoci(loci);
if (clickedSegment === undefined) return;
if (clickedSegment === this.currentState.value.selectedSegment) {
this.actionSelectSegment(undefined);
} else {
this.actionSelectSegment(clickedSegment);
}
});
this.subscribeObservable(
this.highlightRequest.pipe(throttleTime(50, undefined, { leading: true, trailing: true })),
async segment => await this.highlightSegment(segment)
);
this.subscribeObservable(
this.currentState.pipe(distinctUntilChanged((a, b) => a.selectedSegment === b.selectedSegment)),
async state => {
if (VolsegGlobalStateData.getGlobalState(this.plugin)?.selectionMode) await this.selectSegment(state.selectedSegment);
}
);
}
async unregister() {
this.plugin.managers.lociLabels.removeProvider(this.labelProvider);
}
async loadVolume() {
const result = await this.volumeData.loadVolume();
if (result) {
const isovalue = result.isovalue.kind === 'relative' ? result.isovalue.relativeValue : result.isovalue.absoluteValue;
await this.updateStateNode({ volumeIsovalueKind: result.isovalue.kind, volumeIsovalueValue: isovalue });
}
}
async loadSegmentations() {
await this.latticeSegmentationData.loadSegmentation();
await this.meshSegmentationData.loadSegmentation();
await this.actionShowSegments(this.metadata.allSegmentIds);
}
actionHighlightSegment(segment?: Segment) {
this.highlightRequest.next(segment);
}
async actionToggleSegment(segment: number) {
const current = this.currentState.value.visibleSegments.map(seg => seg.segmentId);
if (current.includes(segment)) {
await this.actionShowSegments(current.filter(s => s !== segment));
} else {
await this.actionShowSegments([...current, segment]);
}
}
async actionToggleAllSegments() {
const current = this.currentState.value.visibleSegments.map(seg => seg.segmentId);
if (current.length !== this.metadata.allSegments.length) {
await this.actionShowSegments(this.metadata.allSegmentIds);
} else {
await this.actionShowSegments([]);
}
}
async actionSelectSegment(segment?: number) {
if (segment !== undefined && this.currentState.value.visibleSegments.find(s => s.segmentId === segment) === undefined) {
// first make the segment visible if it is not
await this.actionToggleSegment(segment);
}
await this.updateStateNode({ selectedSegment: segment });
}
async actionSetOpacity(opacity: number) {
if (opacity === this.getStateNode().obj?.data.segmentOpacity) return;
this.latticeSegmentationData.updateOpacity(opacity);
this.meshSegmentationData.updateOpacity(opacity);
await this.updateStateNode({ segmentOpacity: opacity });
}
async actionShowFittedModel(pdbIds: string[]) {
await this.modelData.showPdbs(pdbIds);
await this.updateStateNode({ visibleModels: pdbIds.map(pdbId => ({ pdbId: pdbId })) });
}
async actionSetVolumeVisual(type: 'isosurface' | 'direct-volume' | 'off') {
await this.volumeData.setVolumeVisual(type);
await this.updateStateNode({ volumeType: type });
}
async actionUpdateVolumeVisual(params: SimpleVolumeParamValues) {
await this.volumeData.updateVolumeVisual(params);
await this.updateStateNode({
volumeType: params.volumeType,
volumeOpacity: params.opacity,
});
}
private async actionShowSegments(segments: number[]) {
await this.latticeSegmentationData.showSegments(segments);
await this.meshSegmentationData.showSegments(segments);
await this.updateStateNode({ visibleSegments: segments.map(s => ({ segmentId: s })) });
}
private async highlightSegment(segment?: Segment) {
await PluginCommands.Interactivity.ClearHighlights(this.plugin);
if (segment) {
await this.latticeSegmentationData.highlightSegment(segment);
await this.meshSegmentationData.highlightSegment(segment);
}
}
private async selectSegment(segment: number) {
this.plugin.managers.interactivity.lociSelects.deselectAll();
await this.latticeSegmentationData.selectSegment(segment);
await this.meshSegmentationData.selectSegment(segment);
await this.highlightSegment();
}
private async updateStateNode(params: Partial<VolsegStateData>) {
const oldParams = this.getStateNode().transform.params;
const newParams = { ...oldParams, ...params };
const state = this.plugin.state.data;
const update = state.build().to(this.getStateNode().transform.ref).update(newParams);
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
/** Find the nodes under this entry root which have all of the given tags. */
findNodesByTags(...tags: string[]) {
return this.plugin.state.data.selectQ(q => {
let builder = q.byRef(this.ref).subtree();
for (const tag of tags) builder = builder.withTag(tag);
return builder;
});
}
newUpdate() {
if (this.ref !== '') {
return this.plugin.build().to(this.ref);
} else {
return this.plugin.build().toRoot();
}
}
private readonly labelProvider: LociLabelProvider = {
label: (loci: Loci): string | undefined => {
const segmentId = this.getSegmentIdFromLoci(loci);
if (segmentId === undefined) return;
const segment = this.metadata.getSegment(segmentId);
if (!segment) return;
const annotLabels = segment.biological_annotation.external_references.map(annot => `${applyEllipsis(annot.label)} [${annot.resource}:${annot.accession}]`);
if (annotLabels.length === 0) return;
if (annotLabels.length > MAX_ANNOTATIONS_IN_LABEL + 1) {
const nHidden = annotLabels.length - MAX_ANNOTATIONS_IN_LABEL;
annotLabels.length = MAX_ANNOTATIONS_IN_LABEL;
annotLabels.push(`(${nHidden} more annotations, click on the segment to see all)`);
}
return '<hr class="msp-highlight-info-hr"/>' + annotLabels.filter(isDefined).join('<br/>');
}
};
private getSegmentIdFromLoci(loci: Loci): number | undefined {
if (Volume.Segment.isLoci(loci) && loci.volume._propertyData.ownerId === this.ref) {
if (loci.segments.length === 1) {
return loci.segments[0];
}
}
if (ShapeGroup.isLoci(loci)) {
const meshData = (loci.shape.sourceData ?? {}) as MeshlistData;
if (meshData.ownerId === this.ref && meshData.segmentId !== undefined) {
return meshData.segmentId;
}
}
}
async setTryUseGpu(tryUseGpu: boolean) {
await Promise.all([
this.volumeData.setTryUseGpu(tryUseGpu),
this.latticeSegmentationData.setTryUseGpu(tryUseGpu),
]);
}
async setSelectionMode(selectSegments: boolean) {
if (selectSegments) {
await this.selectSegment(this.currentState.value.selectedSegment);
} else {
this.plugin.managers.interactivity.lociSelects.deselectAll();
}
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Volume } from '../../mol-model/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { VolumeFromSegmentationCif } from '../../mol-plugin-state/transforms/volume';
import { PluginCommands } from '../../mol-plugin/commands';
import { Color } from '../../mol-util/color';
import { Segment } from './volseg-api/data';
import { BOX, VolsegEntryData, MAX_VOXELS } from './entry-root';
import { VolumeVisualParams } from './entry-volume';
import { VolsegGlobalStateData } from './global-state';
const GROUP_TAG = 'lattice-segmentation-group';
const SEGMENT_VISUAL_TAG = 'lattice-segment-visual';
const DEFAULT_SEGMENT_COLOR = Color.fromNormalizedRgb(0.8, 0.8, 0.8);
export class VolsegLatticeSegmentationData {
private entryData: VolsegEntryData;
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
async loadSegmentation() {
const hasLattices = this.entryData.metadata.raw.grid.segmentation_lattices.segmentation_lattice_ids.length > 0;
if (hasLattices) {
const url = this.entryData.api.latticeUrl(this.entryData.source, this.entryData.entryId, 0, BOX, MAX_VOXELS);
let group = this.entryData.findNodesByTags(GROUP_TAG)[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup,
{ label: 'Segmentation', description: 'Lattice' }, { tags: [GROUP_TAG], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const segmentLabels = this.entryData.metadata.allSegments.map(seg => ({ id: seg.id, label: seg.biological_annotation.name ? `<b>${seg.biological_annotation.name}</b>` : '' }));
const volumeNode = await this.entryData.newUpdate().to(group)
.apply(Download, { url, isBinary: true, label: `Segmentation Data: ${url}` })
.apply(ParseCif)
.apply(VolumeFromSegmentationCif, { blockHeader: 'SEGMENTATION_DATA', segmentLabels: segmentLabels, ownerId: this.entryData.ref })
.commit();
const volumeData = volumeNode.data as Volume;
const segmentation = Volume.Segmentation.get(volumeData);
const segmentIds: number[] = Array.from(segmentation?.segments.keys() ?? []);
await this.entryData.newUpdate().to(volumeNode)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.entryData.plugin, volumeData, {
type: 'segment',
typeParams: { tryUseGpu: VolsegGlobalStateData.getGlobalState(this.entryData.plugin)?.tryUseGpu },
color: 'volume-segment',
colorParams: { palette: this.createPalette(segmentIds) },
}), { tags: [SEGMENT_VISUAL_TAG] }).commit();
}
}
private createPalette(segmentIds: number[]) {
const colorMap = new Map<number, Color>();
for (const segment of this.entryData.metadata.allSegments) {
const color = Color.fromNormalizedArray(segment.colour, 0);
colorMap.set(segment.id, color);
}
if (colorMap.size === 0) return undefined;
for (const segid of segmentIds) {
colorMap.get(segid);
}
const colors = segmentIds.map(segid => colorMap.get(segid) ?? DEFAULT_SEGMENT_COLOR);
return { name: 'colors' as const, params: { list: { kind: 'set' as const, colors: colors } } };
}
async updateOpacity(opacity: number) {
const reprs = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG);
const update = this.entryData.newUpdate();
for (const s of reprs) {
update.to(s).update(StateTransforms.Representation.VolumeRepresentation3D, p => { p.type.params.alpha = opacity; });
}
return await update.commit();
}
private makeLoci(segments: number[]) {
const vis = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG)[0];
if (!vis) return undefined;
const repr = vis.obj?.data.repr;
const wholeLoci = repr.getAllLoci()[0];
if (!wholeLoci || !Volume.Segment.isLoci(wholeLoci)) return undefined;
return { loci: Volume.Segment.Loci(wholeLoci.volume, segments), repr: repr };
}
async highlightSegment(segment: Segment) {
const segmentLoci = this.makeLoci([segment.id]);
if (!segmentLoci) return;
this.entryData.plugin.managers.interactivity.lociHighlights.highlight(segmentLoci, false);
}
async selectSegment(segment?: number) {
if (segment === undefined || segment < 0) return;
const segmentLoci = this.makeLoci([segment]);
if (!segmentLoci) return;
this.entryData.plugin.managers.interactivity.lociSelects.select(segmentLoci, false);
}
/** Make visible the specified set of lattice segments */
async showSegments(segments: number[]) {
const repr = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG)[0];
if (!repr) return;
const selectedSegment = this.entryData.currentState.value.selectedSegment;
const mustReselect = segments.includes(selectedSegment) && !repr.params?.values.type.params.segments.includes(selectedSegment);
const update = this.entryData.newUpdate();
update.to(repr).update(StateTransforms.Representation.VolumeRepresentation3D, p => { p.type.params.segments = segments; });
await update.commit();
if (mustReselect) {
await this.selectSegment(this.entryData.currentState.value.selectedSegment);
}
}
async setTryUseGpu(tryUseGpu: boolean) {
const visuals = this.entryData.findNodesByTags(SEGMENT_VISUAL_TAG);
for (const visual of visuals) {
const oldParams: VolumeVisualParams = visual.transform.params;
if (oldParams.type.params.tryUseGpu === !tryUseGpu) {
const newParams = { ...oldParams, type: { ...oldParams.type, params: { ...oldParams.type.params, tryUseGpu: tryUseGpu } } };
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Choice } from './helpers';
export const VolumeTypeChoice = new Choice({ 'isosurface': 'Isosurface', 'direct-volume': 'Direct volume', 'off': 'Off' }, 'isosurface');
export type VolumeType = Choice.Values<typeof VolumeTypeChoice>
export const VolsegStateParams = {
volumeType: VolumeTypeChoice.PDSelect(),
volumeIsovalueKind: PD.Select('relative', [['relative', 'Relative'], ['absolute', 'Absolute']]),
volumeIsovalueValue: PD.Numeric(1),
volumeOpacity: PD.Numeric(0.2, { min: 0, max: 1, step: 0.05 }),
segmentOpacity: PD.Numeric(1, { min: 0, max: 1, step: 0.05 }),
selectedSegment: PD.Numeric(-1, { step: 1 }),
visibleSegments: PD.ObjectList({ segmentId: PD.Numeric(0) }, s => s.segmentId.toString()),
visibleModels: PD.ObjectList({ pdbId: PD.Text('') }, s => s.pdbId.toString()),
};
export type VolsegStateData = PD.Values<typeof VolsegStateParams>;
export class VolsegState extends PluginStateObject.Create<VolsegStateData>({ name: 'Vol & Seg Entry State', typeClass: 'Data' }) { }
export const VOLSEG_STATE_FROM_ENTRY_TRANSFORMER_NAME = 'volseg-state-from-entry'; // defined here to avoid cyclic dependency

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Vec2 } from '../../mol-math/linear-algebra';
import { Volume } from '../../mol-model/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { Download } from '../../mol-plugin-state/transforms/data';
import { CreateGroup } from '../../mol-plugin-state/transforms/misc';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { PluginCommands } from '../../mol-plugin/commands';
import { StateObjectSelector } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { BOX, VolsegEntryData, MAX_VOXELS } from './entry-root';
import { VolsegStateParams, VolumeTypeChoice } from './entry-state';
import * as ExternalAPIs from './external-api';
import { VolsegGlobalStateData } from './global-state';
const GROUP_TAG = 'volume-group';
export const VOLUME_VISUAL_TAG = 'volume-visual';
const DIRECT_VOLUME_RELATIVE_PEAK_HALFWIDTH = 0.5;
export type VolumeVisualParams = ReturnType<typeof createVolumeRepresentationParams>;
interface VolumeStats { min: number, max: number, mean: number, sigma: number };
export const SimpleVolumeParams = {
volumeType: VolumeTypeChoice.PDSelect(),
opacity: PD.Numeric(0.2, { min: 0, max: 1, step: 0.05 }, { hideIf: p => p.volumeType === 'off' }),
};
export type SimpleVolumeParamValues = PD.Values<typeof SimpleVolumeParams>;
export class VolsegVolumeData {
private entryData: VolsegEntryData;
private visualTypeParamCache: { [type: string]: any } = {};
constructor(rootData: VolsegEntryData) {
this.entryData = rootData;
}
async loadVolume() {
const hasVolumes = this.entryData.metadata.raw.grid.volumes.volume_downsamplings.length > 0;
if (hasVolumes) {
const isoLevelPromise = ExternalAPIs.tryGetIsovalue(this.entryData.metadata.raw.grid.general.source_db_id ?? this.entryData.entryId);
let group = this.entryData.findNodesByTags(GROUP_TAG)[0]?.transform.ref;
if (!group) {
const newGroupNode = await this.entryData.newUpdate().apply(CreateGroup, { label: 'Volume' }, { tags: [GROUP_TAG], state: { isCollapsed: true } }).commit();
group = newGroupNode.ref;
}
const url = this.entryData.api.volumeUrl(this.entryData.source, this.entryData.entryId, BOX, MAX_VOXELS);
const data = await this.entryData.newUpdate().to(group).apply(Download, { url, isBinary: true, label: `Volume Data: ${url}` }).commit();
const parsed = await this.entryData.plugin.dataFormats.get('dscif')!.parse(this.entryData.plugin, data);
const volumeNode: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[0] ?? parsed.volume;
const volumeData = volumeNode.cell!.obj!.data;
const volumeType = VolsegStateParams.volumeType.defaultValue;
let isovalue = await isoLevelPromise;
if (!isovalue) {
const stats = volumeData.grid.stats;
const maxRelative = (stats.max - stats.mean) / stats.sigma;
if (maxRelative > 1) {
isovalue = { kind: 'relative', value: 1.0 };
} else {
isovalue = { kind: 'relative', value: maxRelative * 0.5 };
}
}
const adjustedIsovalue = Volume.adjustedIsoValue(volumeData, isovalue.value, isovalue.kind);
const visualParams = this.createVolumeVisualParams(volumeData, volumeType);
this.changeIsovalueInVolumeVisualParams(visualParams, adjustedIsovalue, volumeData.grid.stats);
await this.entryData.newUpdate()
.to(volumeNode)
.apply(StateTransforms.Representation.VolumeRepresentation3D, visualParams, { tags: [VOLUME_VISUAL_TAG], state: { isHidden: volumeType === 'off' } })
.commit();
return { isovalue: adjustedIsovalue };
}
}
async setVolumeVisual(type: 'isosurface' | 'direct-volume' | 'off') {
const visual = this.entryData.findNodesByTags(VOLUME_VISUAL_TAG)[0];
if (!visual) return;
const oldParams: VolumeVisualParams = visual.transform.params;
this.visualTypeParamCache[oldParams.type.name] = oldParams.type.params;
if (type === 'off') {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, true); // true means hide, ¯\_(ツ)_/¯
} else {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, false); // true means hide, ¯\_(ツ)_/¯
if (oldParams.type.name === type) return;
const newParams: VolumeVisualParams = {
...oldParams,
type: {
name: type,
params: this.visualTypeParamCache[type] ?? oldParams.type.params,
}
};
const volumeStats = visual.obj?.data.sourceData.grid.stats;
if (!volumeStats) throw new Error(`Cannot get volume stats from volume visual ${visual.transform.ref}`);
this.changeIsovalueInVolumeVisualParams(newParams, undefined, volumeStats);
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
async updateVolumeVisual(newParams: SimpleVolumeParamValues) {
const { volumeType, opacity } = newParams;
const visual = this.entryData.findNodesByTags(VOLUME_VISUAL_TAG)[0];
if (!visual) return;
const oldVisualParams: VolumeVisualParams = visual.transform.params;
this.visualTypeParamCache[oldVisualParams.type.name] = oldVisualParams.type.params;
if (volumeType === 'off') {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, true); // true means hide, ¯\_(ツ)_/¯
} else {
setSubtreeVisibility(this.entryData.plugin.state.data, visual.transform.ref, false); // true means hide, ¯\_(ツ)_/¯
const newVisualParams: VolumeVisualParams = {
...oldVisualParams,
type: {
name: volumeType,
params: this.visualTypeParamCache[volumeType] ?? oldVisualParams.type.params,
}
};
newVisualParams.type.params.alpha = opacity;
const volumeStats = visual.obj?.data.sourceData.grid.stats;
if (!volumeStats) throw new Error(`Cannot get volume stats from volume visual ${visual.transform.ref}`);
this.changeIsovalueInVolumeVisualParams(newVisualParams, undefined, volumeStats);
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newVisualParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
async setTryUseGpu(tryUseGpu: boolean) {
const visuals = this.entryData.findNodesByTags(VOLUME_VISUAL_TAG);
for (const visual of visuals) {
const oldParams: VolumeVisualParams = visual.transform.params;
if (oldParams.type.params.tryUseGpu === !tryUseGpu) {
const newParams = { ...oldParams, type: { ...oldParams.type, params: { ...oldParams.type.params, tryUseGpu: tryUseGpu } } };
const update = this.entryData.newUpdate().to(visual.transform.ref).update(newParams);
await PluginCommands.State.Update(this.entryData.plugin, { state: this.entryData.plugin.state.data, tree: update, options: { doNotUpdateCurrent: true } });
}
}
}
private getIsovalueFromState(): Volume.IsoValue {
const { volumeIsovalueKind, volumeIsovalueValue } = this.entryData.currentState.value;
return volumeIsovalueKind === 'relative'
? Volume.IsoValue.relative(volumeIsovalueValue)
: Volume.IsoValue.absolute(volumeIsovalueValue);
}
private createVolumeVisualParams(volume: Volume, type: 'isosurface' | 'direct-volume' | 'off'): VolumeVisualParams {
if (type === 'off') type = 'isosurface';
return createVolumeRepresentationParams(this.entryData.plugin, volume, {
type: type,
typeParams: { alpha: 0.2, tryUseGpu: VolsegGlobalStateData.getGlobalState(this.entryData.plugin)?.tryUseGpu },
color: 'uniform',
colorParams: { value: Color(0x121212) },
});
}
private changeIsovalueInVolumeVisualParams(params: VolumeVisualParams, isovalue: Volume.IsoValue | undefined, stats: VolumeStats) {
isovalue ??= this.getIsovalueFromState();
switch (params.type.name) {
case 'isosurface':
params.type.params.isoValue = isovalue;
params.type.params.tryUseGpu = VolsegGlobalStateData.getGlobalState(this.entryData.plugin)?.tryUseGpu;
break;
case 'direct-volume':
const absIso = Volume.IsoValue.toAbsolute(isovalue, stats).absoluteValue;
const fractIso = (absIso - stats.min) / (stats.max - stats.min);
const peakHalfwidth = DIRECT_VOLUME_RELATIVE_PEAK_HALFWIDTH * stats.sigma / (stats.max - stats.min);
params.type.params.controlPoints = [
Vec2.create(Math.max(fractIso - peakHalfwidth, 0), 0),
Vec2.create(fractIso, 1),
Vec2.create(Math.min(fractIso + peakHalfwidth, 1), 0),
];
break;
}
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { splitEntryId } from './helpers';
/** Try to get author-defined contour value for isosurface from EMDB API. Return relative value 1.0, if not applicable or fails. */
export async function tryGetIsovalue(entryId: string): Promise<{ kind: 'absolute' | 'relative', value: number } | undefined> {
const split = splitEntryId(entryId);
if (split.source === 'emdb') {
try {
const response = await fetch(`https://www.ebi.ac.uk/emdb/api/entry/map/${split.entryNumber}`);
const json = await response.json();
const contours: any[] = json?.map?.contour_list?.contour;
if (contours && contours.length > 0) {
const theContour = contours.find(c => c.primary) || contours[0];
if (theContour.level === undefined) throw new Error('EMDB API response missing contour level.');
return { kind: 'absolute', value: theContour.level };
}
} catch {
// do nothing
}
}
return undefined;
}
export async function getPdbIdsForEmdbEntry(entryId: string): Promise<string[]> {
const split = splitEntryId(entryId);
const result = [];
if (split.source === 'emdb') {
entryId = entryId.toUpperCase();
const apiUrl = `https://www.ebi.ac.uk/pdbe/api/emdb/entry/fitted/${entryId}`;
try {
const response = await fetch(apiUrl);
if (response.ok) {
const json = await response.json();
const jsonEntry = json[entryId] ?? [];
for (const record of jsonEntry) {
const pdbs = record?.fitted_emdb_id_list?.pdb_id ?? [];
result.push(...pdbs);
}
}
} catch (ex) {
// do nothing
}
}
return result;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { BehaviorSubject } from 'rxjs';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginContext } from '../../mol-plugin/context';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { VolsegEntry } from './entry-root';
import { isDefined } from './helpers';
export const VolsegGlobalStateParams = {
tryUseGpu: PD.Boolean(true, { description: 'Attempt using GPU for faster rendering. \nCaution: with some hardware setups, this might render some objects incorrectly or not at all.' }),
selectionMode: PD.Boolean(true, { description: 'Allow selecting/deselecting a segment by clicking on it.' }),
};
export type VolsegGlobalStateParamValues = PD.Values<typeof VolsegGlobalStateParams>;
export class VolsegGlobalState extends PluginStateObject.CreateBehavior<VolsegGlobalStateData>({ name: 'Vol & Seg Global State' }) { }
export class VolsegGlobalStateData extends PluginBehavior.WithSubscribers<VolsegGlobalStateParamValues> {
private ref: string;
currentState = new BehaviorSubject(PD.getDefaultValues(VolsegGlobalStateParams));
constructor(plugin: PluginContext, params: VolsegGlobalStateParamValues) {
super(plugin, params);
this.currentState.next(params);
}
register(ref: string) {
this.ref = ref;
}
unregister() {
this.ref = '';
}
isRegistered() {
return this.ref !== '';
}
async updateState(plugin: PluginContext, state: Partial<VolsegGlobalStateParamValues>) {
const oldState = this.currentState.value;
const promises = [];
const allEntries = plugin.state.data.selectQ(q => q.ofType(VolsegEntry)).map(cell => cell.obj?.data).filter(isDefined);
if (state.tryUseGpu !== undefined && state.tryUseGpu !== oldState.tryUseGpu) {
for (const entry of allEntries) {
promises.push(entry.setTryUseGpu(state.tryUseGpu));
}
}
if (state.selectionMode !== undefined && state.selectionMode !== oldState.selectionMode) {
for (const entry of allEntries) {
promises.push(entry.setSelectionMode(state.selectionMode));
}
}
await Promise.all(promises);
await plugin.build().to(this.ref).update(state).commit();
}
static getGlobalState(plugin: PluginContext): VolsegGlobalStateParamValues | undefined {
return plugin.state.data.selectQ(q => q.ofType(VolsegGlobalState))[0]?.obj?.data.currentState.value;
}
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Volume } from '../../mol-model/volume';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
import { StateBuilder, StateObjectSelector, StateTransformer } from '../../mol-state';
import { ParamDefinition } from '../../mol-util/param-definition';
import { Source } from './entry-root';
/** Split entry ID (e.g. 'emd-1832') into source ('emdb') and number ('1832') */
export function splitEntryId(entryId: string) {
const PREFIX_TO_SOURCE: { [prefix: string]: Source } = { 'emd': 'emdb' };
const [prefix, entry] = entryId.split('-');
return {
source: PREFIX_TO_SOURCE[prefix] ?? prefix,
entryNumber: entry
};
}
/** Create entry ID (e.g. 'emd-1832') for a combination of source ('emdb') and number ('1832') */
export function createEntryId(source: Source, entryNumber: string | number) {
const SOURCE_TO_PREFIX: { [prefix: string]: string } = { 'emdb': 'emd' };
const prefix = SOURCE_TO_PREFIX[source] ?? source;
return `${prefix}-${entryNumber}`;
}
/**
* Represents a set of values to choose from, with a default value. Example:
* ```
* export const MyChoice = new Choice({ yes: 'I agree', no: 'Nope' }, 'yes');
* export type MyChoiceType = Choice.Values<typeof MyChoice>; // 'yes'|'no'
* ```
*/
export class Choice<T extends string, D extends T> {
readonly defaultValue: D;
readonly options: [T, string][];
private readonly nameDict: { [value in T]: string };
constructor(opts: { [value in T]: string }, defaultValue: D) {
this.defaultValue = defaultValue;
this.options = Object.keys(opts).map(k => [k as T, opts[k as T]]);
this.nameDict = opts;
}
PDSelect(defaultValue?: T, info?: ParamDefinition.Info): ParamDefinition.Select<T> {
return ParamDefinition.Select<T>(defaultValue ?? this.defaultValue, this.options, info);
}
prettyName(value: T): string {
return this.nameDict[value];
}
get values(): T[] {
return this.options.map(([value, pretty]) => value);
}
}
export namespace Choice {
export type Values<T extends Choice<any, any>> = T extends Choice<infer R, any> ? R : any;
}
export function isDefined<T>(x: T | undefined): x is T {
return x !== undefined;
}
export class NodeManager {
private nodes: { [key: string]: StateObjectSelector };
constructor() {
this.nodes = {};
}
private static nodeExists(node: StateObjectSelector): boolean {
try {
return node.checkValid();
} catch {
return false;
}
}
public getNode(key: string): StateObjectSelector | undefined {
const node = this.nodes[key];
if (node && !NodeManager.nodeExists(node)) {
delete this.nodes[key];
return undefined;
}
return node;
}
public getNodes(): StateObjectSelector[] {
return Object.keys(this.nodes).map(key => this.getNode(key)).filter(node => node) as StateObjectSelector[];
}
public deleteAllNodes(update: StateBuilder.Root) {
for (const node of this.getNodes()) {
update.delete(node);
}
this.nodes = {};
}
public hideAllNodes() {
for (const node of this.getNodes()) {
setSubtreeVisibility(node.state!, node.ref, true); // hide
}
}
public async showNode(key: string, factory: () => StateObjectSelector | Promise<StateObjectSelector>, forceVisible: boolean = true) {
let node = this.getNode(key);
if (node) {
if (forceVisible) {
setSubtreeVisibility(node.state!, node.ref, false); // show
}
} else {
node = await factory();
this.nodes[key] = node;
}
return node;
}
}
const CreateTransformer = StateTransformer.builderFactory('volseg');
export const CreateVolume = CreateTransformer({
name: 'create-transformer',
from: PluginStateObject.Root,
to: PluginStateObject.Volume.Data,
params: {
label: ParamDefinition.Text('Volume', { isHidden: true }),
description: ParamDefinition.Text('', { isHidden: true }),
volume: ParamDefinition.Value<Volume>(undefined as any, { isHidden: true }),
}
})({
apply({ params }) {
return new PluginStateObject.Volume.Data(params.volume, { label: params.label, description: params.description });
}
});
export function applyEllipsis(name: string, max_chars: number = 60) {
if (name.length <= max_chars) return name;
const beginning = name.substring(0, max_chars);
let lastSpace = beginning.lastIndexOf(' ');
if (lastSpace === -1) return beginning + '...';
if (lastSpace > 0 && ',;.'.includes(name.charAt(lastSpace - 1))) lastSpace--;
return name.substring(0, lastSpace) + '...';
}
export function lazyGetter<T>(getter: () => T, errorIfUndefined?: string) {
let value: T | undefined = undefined;
return () => {
if (value === undefined) value = getter();
if (errorIfUndefined && value === undefined) throw new Error(errorIfUndefined);
return value;
};
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject as SO } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginConfigItem } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { StateAction } from '../../mol-state';
import { Task } from '../../mol-task';
import { DEFAULT_VOLSEG_SERVER, VolumeApiV2 } from './volseg-api/api';
import { VolsegEntryData, VolsegEntryParamValues, createLoadVolsegParams } from './entry-root';
import { VolsegGlobalState } from './global-state';
import { createEntryId } from './helpers';
import { VolsegEntryFromRoot, VolsegGlobalStateFromRoot, VolsegStateFromEntry } from './transformers';
import { VolsegUI } from './ui';
const DEBUGGING = window.location.hostname === 'localhost';
export const VolsegVolumeServerConfig = {
// DefaultServer: new PluginConfigItem('volseg-volume-server', DEFAULT_VOLUME_SERVER_V2),
DefaultServer: new PluginConfigItem('volseg-volume-server', DEBUGGING ? 'http://localhost:9000/v2' : DEFAULT_VOLSEG_SERVER),
};
export const Volseg = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
name: 'volseg',
category: 'misc',
display: {
name: 'Volseg',
description: 'Volseg'
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
register() {
this.ctx.state.data.actions.add(LoadVolseg);
this.ctx.customStructureControls.set('volseg', VolsegUI as any);
this.initializeEntryLists(); // do not await
const entries = new Map<string, VolsegEntryData>();
this.subscribeObservable(this.ctx.state.data.events.cell.created, o => {
if (o.cell.obj instanceof VolsegEntryData) entries.set(o.ref, o.cell.obj);
});
this.subscribeObservable(this.ctx.state.data.events.cell.removed, o => {
if (entries.has(o.ref)) {
entries.get(o.ref)!.dispose();
entries.delete(o.ref);
}
});
}
unregister() {
this.ctx.state.data.actions.remove(LoadVolseg);
this.ctx.customStructureControls.delete('volseg');
}
private async initializeEntryLists() {
const apiUrl = this.ctx.config.get(VolsegVolumeServerConfig.DefaultServer) ?? DEFAULT_VOLSEG_SERVER;
const api = new VolumeApiV2(apiUrl);
const entryLists = await api.getEntryList(10 ** 6);
Object.values(entryLists).forEach(l => l.sort());
(this.ctx.customState as any).volsegAvailableEntries = entryLists;
}
}
});
export const LoadVolseg = StateAction.build({
display: { name: 'Load Volume & Segmentation' },
from: SO.Root,
params: (a, plugin: PluginContext) => {
const res = createLoadVolsegParams(plugin, (plugin.customState as any).volsegAvailableEntries);
return res;
},
})(({ params, state }, ctx: PluginContext) => Task.create('Loading Volume & Segmentation', taskCtx => {
return state.transaction(async () => {
const entryParams = VolsegEntryParamValues.fromLoadVolsegParamValues(params);
if (entryParams.entryId.trim().length === 0) {
alert('Must specify Entry Id!');
throw new Error('Specify Entry Id');
}
if (!entryParams.entryId.includes('-')) {
// add source prefix if the user omitted it (e.g. 1832 -> emd-1832)
entryParams.entryId = createEntryId(entryParams.source, entryParams.entryId);
}
ctx.behaviors.layout.leftPanelTabName.next('data');
const globalStateNode = ctx.state.data.selectQ(q => q.ofType(VolsegGlobalState))[0];
if (!globalStateNode) {
await state.build().toRoot().apply(VolsegGlobalStateFromRoot, {}, { state: { isGhost: !DEBUGGING } }).commit();
}
const entryNode = await state.build().toRoot().apply(VolsegEntryFromRoot, entryParams).commit();
await state.build().to(entryNode).apply(VolsegStateFromEntry, {}, { state: { isGhost: !DEBUGGING } }).commit();
if (entryNode.data) {
await entryNode.data.loadVolume();
await entryNode.data.loadSegmentations();
}
}).runInContext(taskCtx);
}));

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
import { PluginContext } from '../../mol-plugin/context';
import { StateTransformer } from '../../mol-state';
import { Task } from '../../mol-task';
import { VolsegEntry, VolsegEntryData, createVolsegEntryParams } from './entry-root';
import { VolsegState, VolsegStateParams, VOLSEG_STATE_FROM_ENTRY_TRANSFORMER_NAME } from './entry-state';
import { VolsegGlobalState, VolsegGlobalStateData, VolsegGlobalStateParams } from './global-state';
export const VolsegEntryFromRoot = PluginStateTransform.BuiltIn({
name: 'volseg-entry-from-root',
display: { name: 'Vol & Seg Entry', description: 'Vol & Seg Entry' },
from: PluginStateObject.Root,
to: VolsegEntry,
params: (a, plugin: PluginContext) => createVolsegEntryParams(plugin),
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Load Vol & Seg Entry', async () => {
const data = await VolsegEntryData.create(plugin, params);
return new VolsegEntry(data, { label: data.entryId, description: 'Vol & Seg Entry' });
});
},
update({ b, oldParams, newParams }) {
Object.assign(newParams, oldParams);
console.error('Changing params of existing VolsegEntry node is not allowed');
return StateTransformer.UpdateResult.Unchanged;
}
});
export const VolsegStateFromEntry = PluginStateTransform.BuiltIn({
name: VOLSEG_STATE_FROM_ENTRY_TRANSFORMER_NAME,
display: { name: 'Vol & Seg Entry State', description: 'Vol & Seg Entry State' },
from: VolsegEntry,
to: VolsegState,
params: VolsegStateParams,
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Create Vol & Seg Entry State', async () => {
return new VolsegState(params, { label: 'State' });
});
}
});
export const VolsegGlobalStateFromRoot = PluginStateTransform.BuiltIn({
name: 'volseg-global-state-from-root',
display: { name: 'Vol & Seg Global State', description: 'Vol & Seg Global State' },
from: PluginStateObject.Root,
to: VolsegGlobalState,
params: VolsegGlobalStateParams,
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Create Vol & Seg Global State', async () => {
const data = new VolsegGlobalStateData(plugin, params);
return new VolsegGlobalState(data, { label: 'Global State', description: 'Vol & Seg Global State' });
});
},
update({ b, oldParams, newParams }) {
b.data.currentState.next(newParams);
return StateTransformer.UpdateResult.Updated;
}
});

View File

@@ -0,0 +1,264 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { useCallback, useEffect, useRef, useState } from 'react';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button, ControlRow, ExpandGroup, IconButton } from '../../mol-plugin-ui/controls/common';
import * as Icons from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { Slider } from '../../mol-plugin-ui/controls/slider';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { UpdateTransformControl } from '../../mol-plugin-ui/state/update-transform';
import { PluginContext } from '../../mol-plugin/context';
import { shallowEqualArrays } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { sleep } from '../../mol-util/sleep';
import { VolsegEntry, VolsegEntryData } from './entry-root';
import { SimpleVolumeParams, SimpleVolumeParamValues } from './entry-volume';
import { VolsegGlobalState, VolsegGlobalStateData, VolsegGlobalStateParams } from './global-state';
import { isDefined } from './helpers';
interface VolsegUIData {
globalState?: VolsegGlobalStateData,
availableNodes: VolsegEntry[],
activeNode?: VolsegEntry,
}
namespace VolsegUIData {
export function changeAvailableNodes(data: VolsegUIData, newNodes: VolsegEntry[]): VolsegUIData {
const newActiveNode = newNodes.length > data.availableNodes.length ?
newNodes[newNodes.length - 1]
: newNodes.find(node => node.data.ref === data.activeNode?.data.ref) ?? newNodes[0];
return { ...data, availableNodes: newNodes, activeNode: newActiveNode };
}
export function changeActiveNode(data: VolsegUIData, newActiveRef: string): VolsegUIData {
const newActiveNode = data.availableNodes.find(node => node.data.ref === newActiveRef) ?? data.availableNodes[0];
return { ...data, availableNodes: data.availableNodes, activeNode: newActiveNode };
}
export function equals(data1: VolsegUIData, data2: VolsegUIData) {
return shallowEqualArrays(data1.availableNodes, data2.availableNodes) && data1.activeNode === data2.activeNode && data1.globalState === data2.globalState;
}
}
export class VolsegUI extends CollapsableControls<{}, { data: VolsegUIData }> {
protected defaultState(): CollapsableState & { data: VolsegUIData } {
return {
header: 'Volume & Segmentation',
isCollapsed: true,
brand: { accent: 'orange', svg: Icons.ExtensionSvg },
data: {
globalState: undefined,
availableNodes: [],
activeNode: undefined,
}
};
}
protected renderControls(): JSX.Element | null {
return <VolsegControls plugin={this.plugin} data={this.state.data} setData={d => this.setState({ data: d })} />;
}
componentDidMount(): void {
this.setState({ isHidden: true, isCollapsed: false });
this.subscribe(this.plugin.state.data.events.changed, e => {
const nodes = e.state.selectQ(q => q.ofType(VolsegEntry)).map(cell => cell?.obj).filter(isDefined);
const isHidden = nodes.length === 0;
const newData = VolsegUIData.changeAvailableNodes(this.state.data, nodes);
if (!this.state.data.globalState?.isRegistered()) {
const globalState = e.state.selectQ(q => q.ofType(VolsegGlobalState))[0]?.obj?.data;
if (globalState) newData.globalState = globalState;
}
if (!VolsegUIData.equals(this.state.data, newData) || this.state.isHidden !== isHidden) {
this.setState({ data: newData, isHidden: isHidden });
}
});
}
}
function VolsegControls({ plugin, data, setData }: { plugin: PluginContext, data: VolsegUIData, setData: (d: VolsegUIData) => void }) {
const entryData = data.activeNode?.data;
if (!entryData) {
return <p>No data!</p>;
}
if (!data.globalState) {
return <p>No global state!</p>;
}
const params = {
/** Reference to the active VolsegEntry node */
entry: PD.Select(data.activeNode!.data.ref, data.availableNodes.map(entry => [entry.data.ref, entry.data.entryId]))
};
const values: PD.Values<typeof params> = {
entry: data.activeNode!.data.ref,
};
const globalState = useBehavior(data.globalState.currentState);
return <>
<ParameterControls params={params} values={values} onChangeValues={next => setData(VolsegUIData.changeActiveNode(data, next.entry))} />
<ExpandGroup header='Global options'>
<WaitingParameterControls params={VolsegGlobalStateParams} values={globalState} onChangeValues={async next => await data.globalState?.updateState(plugin, next)} />
</ExpandGroup>
<VolsegEntryControls entryData={entryData} key={entryData.ref} />
</>;
}
function VolsegEntryControls({ entryData }: { entryData: VolsegEntryData }) {
const state = useBehavior(entryData.currentState);
const allSegments = entryData.metadata.allSegments;
const selectedSegment = entryData.metadata.getSegment(state.selectedSegment);
const visibleSegments = state.visibleSegments.map(seg => seg.segmentId);
const visibleModels = state.visibleModels.map(model => model.pdbId);
const allPdbs = entryData.pdbs;
return <>
{/* Title */}
<div style={{ fontWeight: 'bold', padding: 8, paddingTop: 6, paddingBottom: 4, overflow: 'hidden' }}>
{entryData.metadata.raw.annotation?.name ?? 'Unnamed Annotation'}
</div>
{/* Fitted models */}
{allPdbs.length > 0 && <ExpandGroup header='Fitted models in PDB' initiallyExpanded>
{allPdbs.map(pdb =>
<WaitingButton key={pdb} onClick={() => entryData.actionShowFittedModel(visibleModels.includes(pdb) ? [] : [pdb])}
style={{ fontWeight: visibleModels.includes(pdb) ? 'bold' : undefined, textAlign: 'left', marginTop: 1 }}>
{pdb}
</WaitingButton>
)}
</ExpandGroup>}
{/* Volume */}
<VolumeControls entryData={entryData} />
<ExpandGroup header='Segmentation data' initiallyExpanded>
{/* Segment opacity slider */}
<ControlRow label='Opacity' control={
<WaitingSlider min={0} max={1} value={state.segmentOpacity} step={0.05} onChange={async v => await entryData.actionSetOpacity(v)} />
} />
{/* Segment toggles */}
{allSegments.length > 0 && <>
<WaitingButton onClick={async () => { await sleep(20); await entryData.actionToggleAllSegments(); }} style={{ marginTop: 1 }}>
Toggle All segments
</WaitingButton>
<div style={{ maxHeight: 200, overflow: 'hidden', overflowY: 'auto', marginBlock: 1 }}>
{allSegments.map(segment =>
<div style={{ display: 'flex', marginBottom: 1 }} key={segment.id}
onMouseEnter={() => entryData.actionHighlightSegment(segment)}
onMouseLeave={() => entryData.actionHighlightSegment()}>
<Button onClick={() => entryData.actionSelectSegment(segment !== selectedSegment ? segment.id : undefined)}
style={{ fontWeight: segment.id === selectedSegment?.id ? 'bold' : undefined, marginRight: 1, flexGrow: 1, textAlign: 'left' }}>
<div title={segment.biological_annotation.name ?? 'Unnamed segment'} style={{ maxWidth: 240, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{segment.biological_annotation.name ?? 'Unnamed segment'} ({segment.id})
</div>
</Button>
<IconButton svg={visibleSegments.includes(segment.id) ? Icons.VisibilityOutlinedSvg : Icons.VisibilityOffOutlinedSvg}
onClick={() => entryData.actionToggleSegment(segment.id)} />
</div>
)}
</div>
</>}
</ExpandGroup>
{/* Segment annotations */}
<ExpandGroup header='Selected segment annotation' initiallyExpanded>
<div style={{ paddingTop: 4, paddingRight: 8, maxHeight: 300, overflow: 'hidden', overflowY: 'auto' }}>
{!selectedSegment && 'No segment selected'}
{selectedSegment && <b>Segment {selectedSegment.id}:<br />{selectedSegment.biological_annotation.name ?? 'Unnamed segment'}</b>}
{selectedSegment?.biological_annotation.external_references.map(ref =>
<p key={ref.id} style={{ marginTop: 4 }}>
<small>{ref.resource}:{ref.accession}</small><br />
<b>{capitalize(ref.label)}</b><br />
{ref.description}
</p>)}
</div>
</ExpandGroup>
</>;
}
function VolumeControls({ entryData }: { entryData: VolsegEntryData }) {
const vol = useBehavior(entryData.currentVolume);
if (!vol) return null;
const volumeValues: SimpleVolumeParamValues = {
volumeType: vol.state.isHidden ? 'off' : vol.params?.type.name as any,
opacity: vol.params?.type.params.alpha,
};
return <ExpandGroup header='Volume data' initiallyExpanded>
<WaitingParameterControls params={SimpleVolumeParams} values={volumeValues} onChangeValues={async next => { await sleep(20); await entryData.actionUpdateVolumeVisual(next); }} />
<ExpandGroup header='Detailed Volume Params' headerStyle={{ marginTop: 1 }}>
<UpdateTransformControl state={entryData.plugin.state.data} transform={vol} customHeader='none' />
</ExpandGroup>
</ExpandGroup>;
}
type ComponentParams<T extends React.Component<any, any, any> | ((props: any) => JSX.Element)> =
T extends React.Component<infer P, any, any> ? P : T extends (props: infer P) => JSX.Element ? P : never;
function WaitingSlider({ value, onChange, ...etc }: { value: number, onChange: (value: number) => any } & ComponentParams<Slider>) {
const [changing, sliderValue, execute] = useAsyncChange(value);
return <Slider value={sliderValue} disabled={changing} onChange={newValue => execute(onChange, newValue)} {...etc} />;
}
function WaitingButton({ onClick, ...etc }: { onClick: () => any } & ComponentParams<typeof Button>) {
const [changing, _, execute] = useAsyncChange(undefined);
return <Button disabled={changing} onClick={() => execute(onClick, undefined)} {...etc}>
{etc.children}
</Button>;
}
function WaitingParameterControls<T extends PD.Params>({ values, onChangeValues, ...etc }: { values: PD.ValuesFor<T>, onChangeValues: (values: PD.ValuesFor<T>) => any } & ComponentParams<ParameterControls<T>>) {
const [changing, currentValues, execute] = useAsyncChange(values);
return <ParameterControls isDisabled={changing} values={currentValues} onChangeValues={newValue => execute(onChangeValues, newValue)} {...etc} />;
}
function capitalize(text: string) {
const first = text.charAt(0);
const rest = text.slice(1);
return first.toUpperCase() + rest;
}
function useAsyncChange<T>(initialValue: T) {
const [isExecuting, setIsExecuting] = useState(false);
const [value, setValue] = useState(initialValue);
const isMounted = useRef(false);
useEffect(() => setValue(initialValue), [initialValue]);
useEffect(() => {
isMounted.current = true;
return () => { isMounted.current = false; };
}, []);
const execute = useCallback(
async (func: (val: T) => Promise<any>, val: T) => {
setIsExecuting(true);
setValue(val);
try {
await func(val);
} catch (err) {
if (isMounted.current) {
setValue(initialValue);
}
throw err;
} finally {
if (isMounted.current) {
setIsExecuting(false);
}
}
},
[]
);
return [isExecuting, value, execute] as const;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { type Metadata } from './data';
export const DEFAULT_VOLSEG_SERVER = 'https://molstarvolseg.ncbr.muni.cz/v2';
export class VolumeApiV2 {
public volumeServerUrl: string;
public constructor(volumeServerUrl: string = DEFAULT_VOLSEG_SERVER) {
this.volumeServerUrl = volumeServerUrl.replace(/\/$/, ''); // trim trailing slash
}
public entryListUrl(maxEntries: number, keyword?: string): string {
return `${this.volumeServerUrl}/list_entries/${maxEntries}/${keyword ?? ''}`;
}
public metadataUrl(source: string, entryId: string): string {
return `${this.volumeServerUrl}/${source}/${entryId}/metadata`;
}
public volumeUrl(source: string, entryId: string, box: [[number, number, number], [number, number, number]] | null, maxPoints: number): string {
if (box) {
const [[a1, a2, a3], [b1, b2, b3]] = box;
return `${this.volumeServerUrl}/${source}/${entryId}/volume/box/${a1}/${a2}/${a3}/${b1}/${b2}/${b3}?max_points=${maxPoints}`;
} else {
return `${this.volumeServerUrl}/${source}/${entryId}/volume/cell?max_points=${maxPoints}`;
}
}
public latticeUrl(source: string, entryId: string, segmentation: number, box: [[number, number, number], [number, number, number]] | null, maxPoints: number): string {
if (box) {
const [[a1, a2, a3], [b1, b2, b3]] = box;
return `${this.volumeServerUrl}/${source}/${entryId}/segmentation/box/${segmentation}/${a1}/${a2}/${a3}/${b1}/${b2}/${b3}?max_points=${maxPoints}`;
} else {
return `${this.volumeServerUrl}/${source}/${entryId}/segmentation/cell/${segmentation}?max_points=${maxPoints}`;
}
}
public meshUrl_Json(source: string, entryId: string, segment: number, detailLevel: number): string {
return `${this.volumeServerUrl}/${source}/${entryId}/mesh/${segment}/${detailLevel}`;
}
public meshUrl_Bcif(source: string, entryId: string, segment: number, detailLevel: number): string {
return `${this.volumeServerUrl}/${source}/${entryId}/mesh_bcif/${segment}/${detailLevel}`;
}
public volumeInfoUrl(source: string, entryId: string): string {
return `${this.volumeServerUrl}/${source}/${entryId}/volume_info`;
}
public async getEntryList(maxEntries: number, keyword?: string): Promise<{ [source: string]: string[] }> {
const response = await fetch(this.entryListUrl(maxEntries, keyword));
return await response.json();
}
public async getMetadata(source: string, entryId: string): Promise<Metadata> {
const url = this.metadataUrl(source, entryId);
const response = await fetch(url);
if (!response.ok) throw new Error(`Failed to fetch metadata from ${url}`);
return await response.json();
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
export interface Metadata {
grid: {
general: {
details: string,
source_db_name: string,
source_db_id: string,
},
volumes: Volumes,
segmentation_lattices: SegmentationLattices,
segmentation_meshes: SegmentationMeshes,
},
annotation: Annotation | null,
}
export interface Volumes {
volume_downsamplings: number[],
voxel_size: { [downsampling: number]: Vector3 },
origin: Vector3,
grid_dimensions: Vector3,
sampled_grid_dimensions: { [downsampling: number]: Vector3 },
mean: { [downsampling: number]: number },
std: { [downsampling: number]: number },
min: { [downsampling: number]: number },
max: { [downsampling: number]: number },
volume_force_dtype: string,
}
export interface SegmentationLattices {
segmentation_lattice_ids: number[],
segmentation_downsamplings: { [lattice: number]: number[] },
}
export interface SegmentationMeshes {
mesh_component_numbers: {
segment_ids?: {
[segId: number]: {
detail_lvls: {
[detail: number]: {
mesh_ids: {
[meshId: number]: {
num_triangles: number,
num_vertices: number
}
}
}
}
}
}
}
detail_lvl_to_fraction: {
[lvl: number]: number
}
}
export interface Annotation {
name: string,
details: string,
segment_list: Segment[],
}
export interface Segment {
id: number,
colour: number[],
biological_annotation: BiologicalAnnotation,
}
export interface BiologicalAnnotation {
name: string,
external_references: ExternalReference[]
}
export interface ExternalReference {
id: number, resource: string, accession: string, label: string,
description: string
}
type Vector3 = [number, number, number];

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
import { Color } from '../../../mol-util/color';
import { Metadata, Segment } from './data';
export class MetadataWrapper {
raw: Metadata;
private segmentMap?: { [id: number]: Segment };
constructor(rawMetadata: Metadata) {
this.raw = rawMetadata;
}
get allSegments() {
return this.raw.annotation?.segment_list ?? [];
}
get allSegmentIds() {
return this.allSegments.map(segment => segment.id);
}
getSegment(segmentId: number): Segment | undefined {
if (!this.segmentMap) {
this.segmentMap = {};
for (const segment of this.allSegments) {
this.segmentMap[segment.id] = segment;
}
}
return this.segmentMap[segmentId];
}
getSegmentColor(segmentId: number): Color | undefined {
const colorArray = this.getSegment(segmentId)?.colour;
return colorArray ? Color.fromNormalizedArray(colorArray, 0) : undefined;
}
/** Get the list of detail levels available for the given mesh segment. */
getMeshDetailLevels(segmentId: number): number[] {
const segmentIds = this.raw.grid.segmentation_meshes.mesh_component_numbers.segment_ids;
if (!segmentIds) return [];
const details = segmentIds[segmentId].detail_lvls;
return Object.keys(details).map(s => parseInt(s));
}
/** Get the worst available detail level that is not worse than preferredDetail.
* If preferredDetail is null, get the worst detail level overall.
* (worse = greater number) */
getSufficientMeshDetail(segmentId: number, preferredDetail: number | null) {
let availDetails = this.getMeshDetailLevels(segmentId);
if (preferredDetail !== null) {
availDetails = availDetails.filter(det => det <= preferredDetail);
}
return Math.max(...availDetails);
}
/** IDs of all segments available as meshes */
get meshSegmentIds() {
const segmentIds = this.raw.grid.segmentation_meshes.mesh_component_numbers.segment_ids;
if (!segmentIds) return [];
return Object.keys(segmentIds).map(s => parseInt(s));
}
get gridTotalVolume() {
const [vx, vy, vz] = this.raw.grid.volumes.voxel_size[1];
const [gx, gy, gz] = this.raw.grid.volumes.grid_dimensions;
return vx * vy * vz * gx * gy * gz;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 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>
@@ -119,11 +119,11 @@ class Camera implements ICamera {
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> {
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3, snapshot?: Partial<Camera.Snapshot>): Partial<Camera.Snapshot> {
const r = Math.max(radius, 0.01);
const targetDistance = this.getTargetDistance(r);
Vec3.sub(this.deltaDirection, this.target, this.position);
Vec3.sub(this.deltaDirection, snapshot?.target ?? this.target, snapshot?.position ?? this.position);
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection);
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
Vec3.sub(this.newPosition, target, this.deltaDirection);
@@ -137,6 +137,18 @@ class Camera implements ICamera {
return state;
}
getCenter(target: Vec3, radius?: number): Partial<Camera.Snapshot> {
Vec3.sub(this.deltaDirection, this.target, this.position);
Vec3.sub(this.newPosition, target, this.deltaDirection);
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
state.target = Vec3.clone(target);
state.position = Vec3.clone(this.newPosition);
if (radius) state.radius = Math.max(radius, 0.01);
return state;
}
getInvariantFocus(target: Vec3, radius: number, up: Vec3, dir: Vec3): Partial<Camera.Snapshot> {
const r = Math.max(radius, 0.01);
const targetDistance = this.getTargetDistance(r);
@@ -160,6 +172,10 @@ class Camera implements ICamera {
}
}
center(target: Vec3, durationMs?: number) {
this.setState(this.getCenter(target), durationMs);
}
/** Transform point into 2D window coordinates. */
project(out: Vec4, point: Vec3) {
return cameraProject(out, point, this.viewport, this.projectionView);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 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>
@@ -31,7 +31,7 @@ import { PickData } from './passes/pick';
import { PickHelper } from './passes/pick';
import { ImagePass, ImageProps } from './passes/image';
import { Sphere3D } from '../mol-math/geometry';
import { isDebugMode, isTimingMode } from '../mol-util/debug';
import { addConsoleStatsProvider, isDebugMode, isTimingMode, removeConsoleStatsProvider } from '../mol-util/debug';
import { CameraHelperParams } from './helper/camera-helper';
import { produce } from 'immer';
import { HandleHelperParams } from './helper/handle-helper';
@@ -120,6 +120,7 @@ interface Canvas3DContext {
namespace Canvas3DContext {
export const DefaultAttribs = {
powerPreference: 'high-performance' as WebGLContextAttributes['powerPreference'],
failIfMajorPerformanceCaveat: false,
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
@@ -140,8 +141,9 @@ namespace Canvas3DContext {
if (a.enableWboit && a.enableDpoit) throw new Error('Multiple transparency methods not allowed.');
const { failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const { powerPreference, failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const gl = getGLContext(canvas, {
powerPreference,
failIfMajorPerformanceCaveat,
antialias,
preserveDrawingBuffer,
@@ -258,6 +260,7 @@ interface Canvas3D {
notifyDidDraw: boolean,
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly commited: BehaviorSubject<now.Timestamp>
readonly commitQueueSize: BehaviorSubject<number>
readonly reprCount: BehaviorSubject<number>
readonly resized: BehaviorSubject<any>
@@ -304,6 +307,7 @@ namespace Canvas3D {
let startTime = now();
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const commitQueueSize = new BehaviorSubject<number>(0);
const { gl, contextRestored } = webgl;
@@ -438,7 +442,7 @@ namespace Canvas3D {
cam = stereoCamera;
}
if (isTimingMode) webgl.timer.mark('Canvas3D.render');
if (isTimingMode) webgl.timer.mark('Canvas3D.render', true);
const ctx = { renderer, camera: cam, scene, helper };
if (MultiSamplePass.isEnabled(p.multiSample)) {
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
@@ -591,7 +595,11 @@ namespace Canvas3D {
// snapshot the current bounding sphere of visible objects
Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) {
commitQueueSize.next(scene.commitQueueSize);
return false;
}
commitQueueSize.next(0);
if (helper.debug.isEnabled) helper.debug.update();
if (!p.camera.manualReset && (reprCount.value === 0 || shouldResetCamera())) {
@@ -621,6 +629,8 @@ namespace Canvas3D {
attribute: `${(attribute / 1024 / 1024).toFixed(3)} MiB`,
elements: `${(elements / 1024 / 1024).toFixed(3)} MiB`,
});
console.log(webgl.timer.formatedStats());
}
function add(repr: Representation.Any) {
@@ -726,6 +736,8 @@ namespace Canvas3D {
resized.next(+new Date());
}
addConsoleStatsProvider(consoleStats);
return {
webgl,
@@ -788,6 +800,7 @@ namespace Canvas3D {
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
commited,
commitQueueSize,
reprCount,
resized,
setProps: (properties, doNotRequestDraw = false) => {
@@ -903,6 +916,8 @@ namespace Canvas3D {
controls.dispose();
renderer.dispose();
interactionHelper.dispose();
removeConsoleStatsProvider(consoleStats);
}
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,8 @@ import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { PickingId } from '../../mol-geo/geometry/picking';
import { Text } from '../../mol-geo/geometry/text/text';
import { TextBuilder } from '../../mol-geo/geometry/text/text-builder';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
@@ -23,19 +25,36 @@ import { Visual } from '../../mol-repr/visual';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { assertUnreachable } from '../../mol-util/type-helpers';
import { Camera, ICamera } from '../camera';
import { Viewport } from '../camera/util';
// TODO add scale line/grid
const AxesParams = {
...Mesh.Params,
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
alpha: PD.Numeric(0.51, { min: 0, max: 1, step: 0.01 }, { isEssential: true, label: 'Opacity' }),
colorX: PD.Color(ColorNames.red, { isEssential: true }),
colorY: PD.Color(ColorNames.green, { isEssential: true }),
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
location: PD.Select('bottom-left', PD.arrayToOptions(['bottom-left', 'bottom-right', 'top-left', 'top-right'] as const)),
locationOffsetX: PD.Numeric(0),
locationOffsetY: PD.Numeric(0),
originColor: PD.Color(ColorNames.grey),
radiusScale: PD.Numeric(0.075, { min: 0.01, max: 0.3, step: 0.001 }),
showPlanes: PD.Boolean(true),
planeColorXY: PD.Color(ColorNames.grey, { label: 'Plane Color XY' }),
planeColorXZ: PD.Color(ColorNames.grey, { label: 'Plane Color XZ' }),
planeColorYZ: PD.Color(ColorNames.grey, { label: 'Plane Color YZ' }),
showLabels: PD.Boolean(false),
labelX: PD.Text('X'),
labelY: PD.Text('Y'),
labelZ: PD.Text('Z'),
labelColorX: PD.Color(ColorNames.grey),
labelColorY: PD.Color(ColorNames.grey),
labelColorZ: PD.Color(ColorNames.grey),
labelOpacity: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
labelScale: PD.Numeric(0.25, { min: 0.1, max: 1.0, step: 0.01 }),
};
type AxesParams = typeof AxesParams
type AxesProps = PD.Values<AxesParams>
@@ -56,7 +75,9 @@ export class CameraHelper {
axes: { name: 'off', params: {} }
};
private renderObject: GraphicsRenderObject | undefined;
private meshRenderObject: GraphicsRenderObject | undefined;
private textRenderObject: GraphicsRenderObject | undefined;
private pixelRatio = 1;
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
this.scene = Scene.create(webgl, GraphicsRenderVariantsBlended);
@@ -74,9 +95,20 @@ export class CameraHelper {
p.axes.name = props.axes.name;
if (props.axes.name === 'on') {
this.scene.clear();
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
this.renderObject = createAxesRenderObject(params);
this.scene.add(this.renderObject);
this.pixelRatio = this.webgl.pixelRatio;
const params = {
...props.axes.params,
scale: props.axes.params.scale * this.pixelRatio,
labelScale: props.axes.params.labelScale * this.pixelRatio,
};
this.meshRenderObject = createMeshRenderObject(params);
this.scene.add(this.meshRenderObject);
if (props.axes.params.showLabels) {
this.textRenderObject = createTextRenderObject(params);
this.scene.add(this.textRenderObject);
} else {
this.textRenderObject = undefined;
}
this.scene.commit();
Vec3.set(this.camera.position, 0, 0, params.scale * 200);
@@ -94,19 +126,29 @@ export class CameraHelper {
getLoci(pickingId: PickingId) {
const { objectId, groupId, instanceId } = pickingId;
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
if ((
(!this.meshRenderObject || objectId !== this.meshRenderObject.id) &&
(!this.textRenderObject || objectId !== this.textRenderObject.id)
) || groupId === CameraHelperAxis.None) return EmptyLoci;
return CameraAxesLoci(this, groupId, instanceId);
}
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
if (!this.renderObject) return false;
if (!isCameraAxesLoci(loci)) return false;
let changed = false;
const groupCount = this.renderObject.values.uGroupCount.ref.value;
const { elements } = loci;
for (const { groupId, instanceId } of elements) {
const idx = instanceId * groupCount + groupId;
if (apply(Interval.ofSingleton(idx))) changed = true;
if (this.meshRenderObject) {
const groupCount = this.meshRenderObject.values.uGroupCount.ref.value;
for (const { groupId, instanceId } of loci.elements) {
const idx = instanceId * groupCount + groupId;
if (apply(Interval.ofSingleton(idx))) changed = true;
}
}
if (this.textRenderObject) {
const groupCount = this.textRenderObject.values.uGroupCount.ref.value;
for (const { groupId, instanceId } of loci.elements) {
const idx = instanceId * groupCount + groupId;
if (apply(Interval.ofSingleton(idx))) changed = true;
}
}
return changed;
};
@@ -117,42 +159,82 @@ export class CameraHelper {
if (!isCameraAxesLoci(loci)) return false;
if (loci.data !== this) return false;
}
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
return (
Visual.mark(this.meshRenderObject, loci, action, this.eachGroup) ||
Visual.mark(this.textRenderObject, loci, action, this.eachGroup)
);
}
update(camera: ICamera) {
if (!this.renderObject) return;
if (!this.meshRenderObject || this.props.axes.name === 'off') return;
if (this.pixelRatio !== this.webgl.pixelRatio) {
this.setProps(this.props);
}
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(this.scene.view, Vec3.create(
-camera.viewport.width / 2 + r,
-camera.viewport.height / 2 + r,
0
));
const r = this.textRenderObject
? this.textRenderObject.values.boundingSphere.ref.value.radius
: this.meshRenderObject.values.boundingSphere.ref.value.radius;
const l = this.props.axes.params.location;
const ox = this.props.axes.params.locationOffsetX * this.pixelRatio;
const oy = this.props.axes.params.locationOffsetY * this.pixelRatio;
if (l === 'bottom-left') {
Mat4.setTranslation(this.scene.view, Vec3.create(
-camera.viewport.width / 2 + r + ox,
-camera.viewport.height / 2 + r + oy,
0
));
} else if (l === 'bottom-right') {
Mat4.setTranslation(this.scene.view, Vec3.create(
camera.viewport.width / 2 - r - ox,
-camera.viewport.height / 2 + r + oy,
0
));
} else if (l === 'top-left') {
Mat4.setTranslation(this.scene.view, Vec3.create(
-camera.viewport.width / 2 + r + ox,
camera.viewport.height / 2 - r - oy,
0
));
} else if (l === 'top-right') {
Mat4.setTranslation(this.scene.view, Vec3.create(
camera.viewport.width / 2 - r - ox,
camera.viewport.height / 2 - r - oy,
0
));
} else {
assertUnreachable(l);
}
}
}
export const enum CameraHelperAxis {
export enum CameraHelperAxis {
None = 0,
X,
Y,
Z,
XY,
XZ,
YZ
YZ,
Origin
}
function getAxisLabel(axis: number) {
function getAxisLabel(axis: number, cameraHelper: CameraHelper) {
const a = cameraHelper.props.axes;
const x = a.name === 'on' ? a.params.labelX : 'X';
const y = a.name === 'on' ? a.params.labelY : 'Y';
const z = a.name === 'on' ? a.params.labelZ : 'Z';
switch (axis) {
case CameraHelperAxis.X: return 'X Axis';
case CameraHelperAxis.Y: return 'Y Axis';
case CameraHelperAxis.Z: return 'Z Axis';
case CameraHelperAxis.XY: return 'XY Plane';
case CameraHelperAxis.XZ: return 'XZ Plane';
case CameraHelperAxis.YZ: return 'YZ Plane';
case CameraHelperAxis.X: return `${x} Axis`;
case CameraHelperAxis.Y: return `${y} Axis`;
case CameraHelperAxis.Z: return `${z} Axis`;
case CameraHelperAxis.XY: return `${x}${y} Plane`;
case CameraHelperAxis.XZ: return `${x}${z} Plane`;
case CameraHelperAxis.YZ: return `${y}${z} Plane`;
case CameraHelperAxis.Origin: return 'Origin';
default: return 'Axes';
}
}
@@ -160,7 +242,7 @@ function getAxisLabel(axis: number) {
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
void 0 /** bounding sphere */,
() => getAxisLabel(groupId));
() => getAxisLabel(groupId, cameraHelper));
}
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
@@ -197,15 +279,17 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
}
function createAxesMesh(scale: number, mesh?: Mesh) {
function createAxesMesh(props: AxesProps, mesh?: Mesh) {
const state = MeshBuilder.createState(512, 256, mesh);
const radius = 0.075 * scale;
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
const scale = 100 * props.scale;
const radius = props.radiusScale * scale;
const textScale = props.showLabels ? 100 * props.labelScale / 3 : 0;
const x = Vec3.scale(Vec3(), Vec3.unitX, scale - textScale);
const y = Vec3.scale(Vec3(), Vec3.unitY, scale - textScale);
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale - textScale);
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
state.currentGroup = CameraHelperAxis.None;
state.currentGroup = CameraHelperAxis.Origin;
addSphere(state, Vec3.origin, radius, 2);
state.currentGroup = CameraHelperAxis.X;
@@ -220,50 +304,102 @@ function createAxesMesh(scale: number, mesh?: Mesh) {
addSphere(state, z, radius, 2);
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
Vec3.scale(x, x, 0.5);
Vec3.scale(y, y, 0.5);
Vec3.scale(z, z, 0.5);
if (props.showPlanes) {
Vec3.scale(x, x, 0.5);
Vec3.scale(y, y, 0.5);
Vec3.scale(z, z, 0.5);
state.currentGroup = CameraHelperAxis.XY;
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
const xy = Vec3.add(Vec3(), x, y);
MeshBuilder.addTriangle(state, xy, x, y);
MeshBuilder.addTriangle(state, xy, y, x);
state.currentGroup = CameraHelperAxis.XY;
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
const xy = Vec3.add(Vec3(), x, y);
MeshBuilder.addTriangle(state, xy, x, y);
MeshBuilder.addTriangle(state, xy, y, x);
state.currentGroup = CameraHelperAxis.XZ;
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
const xz = Vec3.add(Vec3(), x, z);
MeshBuilder.addTriangle(state, xz, x, z);
MeshBuilder.addTriangle(state, xz, z, x);
state.currentGroup = CameraHelperAxis.XZ;
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
const xz = Vec3.add(Vec3(), x, z);
MeshBuilder.addTriangle(state, xz, x, z);
MeshBuilder.addTriangle(state, xz, z, x);
state.currentGroup = CameraHelperAxis.YZ;
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
const yz = Vec3.add(Vec3(), y, z);
MeshBuilder.addTriangle(state, yz, y, z);
MeshBuilder.addTriangle(state, yz, z, y);
state.currentGroup = CameraHelperAxis.YZ;
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
const yz = Vec3.add(Vec3(), y, z);
MeshBuilder.addTriangle(state, yz, y, z);
MeshBuilder.addTriangle(state, yz, z, y);
}
return MeshBuilder.getMesh(state);
}
function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
function getAxesMeshShape(props: AxesProps, shape?: Shape<Mesh>) {
const scale = 100 * props.scale;
const mesh = createAxesMesh(scale, shape?.geometry);
const mesh = createAxesMesh(props, shape?.geometry);
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
const getColor = (groupId: number) => {
switch (groupId) {
case 1: return props.colorX;
case 2: return props.colorY;
case 3: return props.colorZ;
case CameraHelperAxis.X: return props.colorX;
case CameraHelperAxis.Y: return props.colorY;
case CameraHelperAxis.Z: return props.colorZ;
case CameraHelperAxis.XY: return props.planeColorXY;
case CameraHelperAxis.XZ: return props.planeColorXZ;
case CameraHelperAxis.YZ: return props.planeColorYZ;
case CameraHelperAxis.Origin: return props.originColor;
default: return ColorNames.grey;
}
};
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '');
return Shape.create('axes-mesh', {}, mesh, getColor, () => 1, () => '');
}
function createAxesRenderObject(props: AxesProps) {
const shape = getAxesShape(props);
return Shape.createRenderObject(shape, props);
}
function createMeshRenderObject(props: AxesProps) {
const shape = getAxesMeshShape(props);
return Shape.createRenderObject(shape, {
...PD.getDefaultValues(Mesh.Params),
...props,
ignoreLight: true,
});
}
//
function createAxesText(props: AxesProps, text?: Text) {
const builder = TextBuilder.create(props, 8, 8, text);
const scale = 100 * props.scale;
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
const textScale = 100 * props.labelScale;
builder.add(props.labelX, x[0], x[1], x[2], 0.0, textScale, CameraHelperAxis.X);
builder.add(props.labelY, y[0], y[1], y[2], 0.0, textScale, CameraHelperAxis.Y);
builder.add(props.labelZ, z[0], z[1], z[2], 0.0, textScale, CameraHelperAxis.Z);
return builder.getText();
}
function getAxesTextShape(props: AxesProps, shape?: Shape<Text>) {
const scale = 100 * props.scale;
const text = createAxesText(props, shape?.geometry);
text.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale));
const getColor = (groupId: number) => {
switch (groupId) {
case CameraHelperAxis.X: return props.labelColorX;
case CameraHelperAxis.Y: return props.labelColorY;
case CameraHelperAxis.Z: return props.labelColorZ;
default: return ColorNames.grey;
}
};
return Shape.create('axes-text', {}, text, getColor, () => 1, () => '');
}
function createTextRenderObject(props: AxesProps) {
const shape = getAxesTextShape(props);
return Shape.createRenderObject(shape, {
...PD.getDefaultValues(Text.Params),
...props,
alpha: props.labelOpacity,
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -54,6 +54,7 @@ export class HandleHelper {
};
private renderObject: GraphicsRenderObject | undefined;
private pixelRatio = 1;
private _transform = Mat4();
getBoundingSphere(out: Sphere3D, instanceId: number) {
@@ -71,7 +72,11 @@ export class HandleHelper {
p.handle.name = props.handle.name;
if (props.handle.name === 'on') {
this.scene.clear();
const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
this.pixelRatio = this.webgl.pixelRatio;
const params = {
...props.handle.params,
scale: props.handle.params.scale * this.webgl.pixelRatio
};
this.renderObject = createHandleRenderObject(params);
this.scene.add(this.renderObject);
this.scene.commit();
@@ -91,6 +96,10 @@ export class HandleHelper {
update(camera: Camera, position: Vec3, rotation: Mat3) {
if (!this.renderObject) return;
if (this.pixelRatio !== this.webgl.pixelRatio) {
this.setProps(this.props);
}
Mat4.setTranslation(this.renderObject.values.aTransform.ref.value as unknown as Mat4, position);
Mat4.fromMat3(this.renderObject.values.aTransform.ref.value as unknown as Mat4, rotation);

View File

@@ -19,7 +19,7 @@ type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
type DragEvent = import('../canvas3d').Canvas3D.DragEvent
type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
const enum InputEvent { Move, Click, Drag }
enum InputEvent { Move, Click, Drag }
const tmpPosA = Vec3();
const tmpPos = Vec3();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 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>
@@ -142,7 +142,7 @@ export class DrawPass {
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
@@ -150,7 +150,7 @@ export class DrawPass {
}
}
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
}
this.depthTextureOpaque.detachFramebuffer(this.colorTarget.framebuffer, 'depth');
@@ -196,7 +196,7 @@ export class DrawPass {
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
@@ -204,7 +204,7 @@ export class DrawPass {
}
}
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
}
// render transparent primitives and volumes
@@ -260,7 +260,7 @@ export class DrawPass {
this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
}
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
@@ -268,7 +268,7 @@ export class DrawPass {
}
}
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
@@ -312,7 +312,7 @@ export class DrawPass {
const { x, y, width, height } = camera.viewport;
renderer.setViewport(x, y, width, height);
renderer.update(camera);
renderer.update(camera, scene);
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
this.drawTarget.bind();
@@ -360,7 +360,7 @@ export class DrawPass {
}
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
renderer.update(helper.camera.camera, helper.camera.scene);
renderer.renderBlended(helper.camera.scene, helper.camera.camera);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -27,6 +27,8 @@ export const MarkingParams = {
highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),
highlightEdgeStrength: PD.Numeric(1.0, { min: 0, max: 1, step: 0.1 }),
selectEdgeStrength: PD.Numeric(1.0, { min: 0, max: 1, step: 0.1 }),
ghostEdgeStrength: PD.Numeric(0.3, { min: 0, max: 1, step: 0.1 }, { description: 'Opacity of the hidden edges that are covered by other geometry. When set to 1, one less geometry render pass is done.' }),
innerEdgeFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Factor to multiply the inner edge color with - for added contrast.' }),
};
@@ -101,7 +103,7 @@ export class MarkingPass {
}
update(props: MarkingProps) {
const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props;
const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength, highlightEdgeStrength, selectEdgeStrength } = props;
const { values: edgeValues } = this.edge;
const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio);
@@ -113,8 +115,10 @@ export class MarkingPass {
const { values: overlayValues } = this.overlay;
ValueCell.update(overlayValues.uHighlightEdgeColor, Color.toVec3Normalized(overlayValues.uHighlightEdgeColor.ref.value, highlightEdgeColor));
ValueCell.update(overlayValues.uSelectEdgeColor, Color.toVec3Normalized(overlayValues.uSelectEdgeColor.ref.value, selectEdgeColor));
ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
ValueCell.updateIfChanged(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
ValueCell.updateIfChanged(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
ValueCell.updateIfChanged(overlayValues.uHighlightEdgeStrength, highlightEdgeStrength);
ValueCell.updateIfChanged(overlayValues.uSelectEdgeStrength, selectEdgeStrength);
}
render(viewport: Viewport, target: RenderTarget | undefined) {
@@ -170,6 +174,8 @@ const OverlaySchema = {
uTexSizeInv: UniformSpec('v2'),
uHighlightEdgeColor: UniformSpec('v3'),
uSelectEdgeColor: UniformSpec('v3'),
uHighlightEdgeStrength: UniformSpec('f'),
uSelectEdgeStrength: UniformSpec('f'),
uGhostEdgeStrength: UniformSpec('f'),
uInnerEdgeFactor: UniformSpec('f'),
};
@@ -186,6 +192,8 @@ function getOverlayRenderable(ctx: WebGLContext, edgeTexture: Texture): OverlayR
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
uHighlightEdgeColor: ValueCell.create(Vec3()),
uSelectEdgeColor: ValueCell.create(Vec3()),
uHighlightEdgeStrength: ValueCell.create(1),
uSelectEdgeStrength: ValueCell.create(1),
uGhostEdgeStrength: ValueCell.create(0),
uInnerEdgeFactor: ValueCell.create(0),
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -172,7 +172,7 @@ export class PickPass {
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: 'pick' | 'depth', pickType: number) {
renderer.clear(false);
renderer.update(camera);
renderer.update(camera, scene);
renderer.renderPick(scene.primitives, camera, variant, null, pickType);
if (helper.handle.isEnabled) {
@@ -181,7 +181,7 @@ export class PickPass {
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
renderer.update(helper.camera.camera, helper.camera.scene);
renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null, pickType);
}
}
@@ -291,7 +291,7 @@ export class PickHelper {
}
private render(camera: Camera | StereoCamera) {
if (isTimingMode) this.webgl.timer.mark('PickHelper.render');
if (isTimingMode) this.webgl.timer.mark('PickHelper.render', true);
const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this;
const { renderer, scene, helper } = this;

View File

@@ -1,8 +1,9 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 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>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
@@ -30,6 +31,8 @@ import { SmaaParams, SmaaPass } from './smaa';
import { isTimingMode } from '../../mol-util/debug';
import { BackgroundParams, BackgroundPass } from './background';
import { AssetManager } from '../../mol-util/assets';
import { Light } from '../../mol-gl/renderer';
import { shadows_frag } from '../../mol-gl/shader/shadows.frag';
const OutlinesSchema = {
...QuadSchema,
@@ -42,10 +45,12 @@ const OutlinesSchema = {
uFar: UniformSpec('f'),
uMaxPossibleViewZDiff: UniformSpec('f'),
dTransparentOutline: DefineSpec('boolean'),
};
type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture): OutlinesRenderable {
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
const width = depthTextureOpaque.getWidth();
const height = depthTextureOpaque.getHeight();
@@ -60,6 +65,8 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
uFar: ValueCell.create(10000),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
dTransparentOutline: ValueCell.create(transparentOutline),
};
const schema = { ...OutlinesSchema };
@@ -69,6 +76,64 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
return createComputeRenderable(renderItem, values);
}
const ShadowsSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
dSteps: DefineSpec('number'),
uMaxDistance: UniformSpec('f'),
uTolerance: UniformSpec('f'),
uBias: UniformSpec('f'),
uLightDirection: UniformSpec('v3[]'),
uLightColor: UniformSpec('v3[]'),
dLightCount: DefineSpec('number'),
};
type ShadowsRenderable = ComputeRenderable<Values<typeof ShadowsSchema>>
function getShadowsRenderable(ctx: WebGLContext, depthTexture: Texture): ShadowsRenderable {
const width = depthTexture.getWidth();
const height = depthTexture.getHeight();
const values: Values<typeof ShadowsSchema> = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uTexSize: ValueCell.create(Vec2.create(width, height)),
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
dSteps: ValueCell.create(1),
uMaxDistance: ValueCell.create(3.0),
uTolerance: ValueCell.create(1.0),
uBias: ValueCell.create(0.6),
uLightDirection: ValueCell.create([]),
uLightColor: ValueCell.create([]),
dLightCount: ValueCell.create(0),
};
const schema = { ...ShadowsSchema };
const shaderCode = ShaderCode('shadows', quad_vert, shadows_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
@@ -204,6 +269,7 @@ const PostprocessingSchema = {
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tShadows: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
@@ -217,23 +283,30 @@ const PostprocessingSchema = {
uTransparentBackground: UniformSpec('b'),
uMaxPossibleViewZDiff: UniformSpec('f'),
uInvProjection: UniformSpec('m4'),
dOcclusionEnable: DefineSpec('boolean'),
uOcclusionOffset: UniformSpec('v2'),
dShadowEnable: DefineSpec('boolean'),
dOutlineEnable: DefineSpec('boolean'),
dOutlineScale: DefineSpec('number'),
uOutlineThreshold: UniformSpec('f'),
dTransparentOutline: DefineSpec('boolean'),
};
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture, transparentOutline: boolean): PostprocessingRenderable {
const values: Values<typeof PostprocessingSchema> = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
tColor: ValueCell.create(colorTexture),
tDepthOpaque: ValueCell.create(depthTextureOpaque),
tDepthTransparent: ValueCell.create(depthTextureTransparent),
tShadows: ValueCell.create(shadowsTexture),
tOutlines: ValueCell.create(outlinesTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
@@ -247,13 +320,18 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
uTransparentBackground: ValueCell.create(false),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
uInvProjection: ValueCell.create(Mat4.identity()),
dOcclusionEnable: ValueCell.create(true),
uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
dShadowEnable: ValueCell.create(false),
dOutlineEnable: ValueCell.create(false),
dOutlineScale: ValueCell.create(1),
uOutlineThreshold: ValueCell.create(0.33),
dTransparentOutline: ValueCell.create(transparentOutline),
};
const schema = { ...PostprocessingSchema };
@@ -274,11 +352,21 @@ export const PostprocessingParams = {
}),
off: PD.Group({})
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
shadow: PD.MappedStatic('off', {
on: PD.Group({
steps: PD.Numeric(1, { min: 1, max: 64, step: 1 }),
bias: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
maxDistance: PD.Numeric(3, { min: 0, max: 256, step: 1 }),
tolerance: PD.Numeric(1.0, { min: 0.0, max: 10.0, step: 0.1 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Simplistic shadows' }),
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 }),
color: PD.Color(Color(0x000000)),
includeTransparent: PD.Boolean(true, { description: 'Whether to show outline for transparent objects' }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Draw outline around 3D objects' }),
@@ -289,15 +377,16 @@ export const PostprocessingParams = {
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
background: PD.Group(BackgroundParams, { isFlat: true }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
export class PostprocessingPass {
static isEnabled(props: PostprocessingProps) {
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
return props.occlusion.name === 'on' || props.shadow.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
}
static isOutlineEnabled(props: PostprocessingProps) {
return props.outline.name === 'on';
static isTransparentOutlineEnabled(props: PostprocessingProps) {
return props.outline.name === 'on' && props.outline.params.includeTransparent;
}
readonly target: RenderTarget;
@@ -305,6 +394,9 @@ export class PostprocessingPass {
private readonly outlinesTarget: RenderTarget;
private readonly outlinesRenderable: OutlinesRenderable;
private readonly shadowsTarget: RenderTarget;
private readonly shadowsRenderable: ShadowsRenderable;
private readonly ssaoFramebuffer: Framebuffer;
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
@@ -348,7 +440,10 @@ export class PostprocessingPass {
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent, true);
this.shadowsTarget = webgl.createRenderTarget(width, height, false);
this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
this.ssaoFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
@@ -357,7 +452,9 @@ export class PostprocessingPass {
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
this.downsampledDepthTarget = drawPass.packedDepth
? webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear', 'rgba')
: webgl.createRenderTarget(sw, sh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
@@ -370,10 +467,10 @@ export class PostprocessingPass {
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
this.ssaoRenderable = getSsaoRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture);
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
this.background = new BackgroundPass(webgl, assetManager, width, height);
}
@@ -389,12 +486,14 @@ export class PostprocessingPass {
const sh = Math.floor(height * this.ssaoScale);
this.target.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.shadowsTarget.setSize(width, height);
this.downsampledDepthTarget.setSize(sw, sh);
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.shadowsRenderable.values.uTexSize, Vec2.set(this.shadowsRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
@@ -404,25 +503,29 @@ export class PostprocessingPass {
}
}
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
let needsUpdateShadows = false;
let needsUpdateMain = false;
let needsUpdateSsao = false;
let needsUpdateSsaoBlur = false;
let needsUpdateOutlines = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
const outlinesEnabled = props.outline.name === 'on';
const shadowsEnabled = props.shadow.name === 'on';
const occlusionEnabled = props.occlusion.name === 'on';
const invProjection = Mat4.identity();
Mat4.invert(invProjection, camera.projection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const v = camera.viewport;
if (props.occlusion.name === 'on') {
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const b = this.ssaoRenderable.values.uBounds;
const v = camera.viewport;
const s = this.ssaoScale;
Vec4.set(b.ref.value,
Math.floor(v.x * s) / (w * s),
@@ -482,7 +585,7 @@ export class PostprocessingPass {
this.ssaoDepthBlurProxyTexture.define(sw, sh);
if (this.ssaoScale === 1) {
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureTransparent);
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureOpaque);
} else {
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
}
@@ -494,11 +597,44 @@ export class PostprocessingPass {
}
}
if (props.shadow.name === 'on') {
ValueCell.update(this.shadowsRenderable.values.uProjection, camera.projection);
ValueCell.update(this.shadowsRenderable.values.uInvProjection, invProjection);
Vec4.set(this.shadowsRenderable.values.uBounds.ref.value,
v.x / w,
v.y / h,
(v.x + v.width) / w,
(v.y + v.height) / h
);
ValueCell.update(this.shadowsRenderable.values.uBounds, this.shadowsRenderable.values.uBounds.ref.value);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.shadowsRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uMaxDistance, props.shadow.params.maxDistance);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uTolerance, props.shadow.params.tolerance);
ValueCell.updateIfChanged(this.shadowsRenderable.values.uBias, props.shadow.params.bias);
if (this.shadowsRenderable.values.dSteps.ref.value !== props.shadow.params.steps) {
ValueCell.update(this.shadowsRenderable.values.dSteps, props.shadow.params.steps);
needsUpdateShadows = true;
}
ValueCell.update(this.shadowsRenderable.values.uLightDirection, light.direction);
ValueCell.update(this.shadowsRenderable.values.uLightColor, light.color);
if (this.shadowsRenderable.values.dLightCount.ref.value !== light.count) {
ValueCell.update(this.shadowsRenderable.values.dLightCount, light.count);
needsUpdateShadows = true;
}
}
if (props.outline.name === 'on') {
let { threshold } = props.outline.params;
let { threshold, includeTransparent } = props.outline.params;
const transparentOutline = includeTransparent ?? true;
// orthographic needs lower threshold
if (camera.state.mode === 'orthographic') threshold /= 5;
const factor = Math.pow(1000, threshold) / 1000;
const factor = Math.pow(1000, threshold / 10) / 1000;
// use radiusMax for stable outlines when zooming
const maxPossibleViewZDiff = factor * camera.state.radiusMax;
const outlineScale = props.outline.params.scale - 1;
@@ -506,12 +642,18 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateOutlines = true; }
ValueCell.updateIfChanged(this.outlinesRenderable.values.dTransparentOutline, transparentOutline);
ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.update(this.renderable.values.uInvProjection, invProjection);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dTransparentOutline, transparentOutline);
}
ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
@@ -522,11 +664,22 @@ export class PostprocessingPass {
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.dShadowEnable.ref.value !== shadowsEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dShadowEnable, shadowsEnabled);
if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
if (needsUpdateOutlines) {
this.outlinesRenderable.update();
}
if (needsUpdateShadows) {
this.shadowsRenderable.update();
}
if (needsUpdateSsao) {
this.ssaoRenderable.update();
}
@@ -564,15 +717,20 @@ export class PostprocessingPass {
this.transparentBackground = value;
}
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render');
this.updateState(camera, transparentBackground, backgroundColor, props);
this.updateState(camera, transparentBackground, backgroundColor, props, light);
if (props.outline.name === 'on') {
this.outlinesTarget.bind();
this.outlinesRenderable.render();
}
if (props.shadow.name === 'on') {
this.shadowsTarget.bind();
this.shadowsRenderable.render();
}
// don't render occlusion if offset is given,
// which will reuse the existing occlusion
if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {

View File

@@ -93,20 +93,35 @@ namespace Column {
return !!v && !!(v as Column<any>).schema && !!(v as Column<any>).value;
}
export const enum ValueKind {
// Value kinds are accessed very of often
// Using a const enum is an internal optimization and is defined separately to better support
// compiling with isolatedModules flag in 3rd party use-cases of Mol*.
export const enum ValueKinds {
/** Defined value (= 0) */
Present = 0,
/** Expressed in CIF as `.` */
/** Expressed in CIF as `.` (= 1) */
NotPresent = 1,
/** Expressed in CIF as `?` */
/** Expressed in CIF as `?` (= 2) */
Unknown = 2
}
export const ValueKind = {
/** Defined value (= 0) */
Present: ValueKinds.Present,
/** Expressed in CIF as `.` (= 1) */
NotPresent: ValueKinds.NotPresent,
/** Expressed in CIF as `?` (= 2) */
Unknown: ValueKinds.Unknown
} as const;
export type ValueKind = (typeof ValueKind)[keyof typeof ValueKinds];
export function Undefined<T extends Schema>(rowCount: number, schema: T): Column<T['T']> {
return constColumn(schema['T'], rowCount, schema, ValueKind.NotPresent);
return constColumn(schema['T'], rowCount, schema, ValueKinds.NotPresent);
}
export function ofConst<T extends Schema>(v: T['T'], rowCount: number, type: T): Column<T['T']> {
return constColumn(v, rowCount, type, ValueKind.Present);
return constColumn(v, rowCount, type, ValueKinds.Present);
}
export function ofLambda<T extends Schema>(spec: LambdaSpec<T>): Column<T['T']> {
@@ -256,7 +271,7 @@ function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schem
return {
schema: schema,
__array: void 0,
isDefined: valueKind === Column.ValueKind.Present,
isDefined: valueKind === Column.ValueKinds.Present,
rowCount,
value,
valueKind: row => valueKind,
@@ -276,7 +291,7 @@ function lambdaColumn<T extends Column.Schema>({ value, valueKind, areValuesEqua
isDefined: true,
rowCount,
value,
valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
valueKind: valueKind ? valueKind : row => Column.ValueKinds.Present,
toArray: params => {
const { array, start } = ColumnHelpers.createArray(rowCount, params);
for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start);
@@ -304,7 +319,7 @@ function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Colu
isDefined: true,
rowCount,
value,
valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
valueKind: valueKind ? valueKind : row => Column.ValueKinds.Present,
toArray: schema.valueType === 'str'
? (schema as Column.Schema.Str).transform === 'lowercase'
? params => {

View File

@@ -85,7 +85,7 @@ namespace Table {
rowCount,
schema: schema[k],
value: r => rows[r][k],
valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present
valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKinds.NotPresent : Column.ValueKinds.Present
});
}
return ret as R;
@@ -267,7 +267,7 @@ namespace Table {
StringBuilder.write(sb, '|');
for (let i = 0; i < cols.length; i++) {
const c = table[cols[i]];
if (c.valueKind(r) === Column.ValueKind.Present) {
if (c.valueKind(r) === Column.ValueKinds.Present) {
StringBuilder.write(sb, c.value(r));
StringBuilder.write(sb, '|');
} else {

View File

@@ -132,8 +132,8 @@ export namespace BaseGeometry {
ValueCell.updateIfChanged(values.uBumpiness, props.material.bumpiness);
const clip = Clip.getClip(props.clip);
ValueCell.update(values.dClipObjectCount, clip.objects.count);
ValueCell.update(values.dClipVariant, clip.variant);
ValueCell.updateIfChanged(values.dClipObjectCount, clip.objects.count);
ValueCell.updateIfChanged(values.dClipVariant, clip.variant);
ValueCell.update(values.uClipObjectType, clip.objects.type);
ValueCell.update(values.uClipObjectInvert, clip.objects.invert);
ValueCell.update(values.uClipObjectPosition, clip.objects.position);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -158,6 +158,7 @@ export namespace Cylinders {
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
};
@@ -213,7 +214,7 @@ export namespace Cylinders {
const padding = getMaxSize(size) * props.sizeFactor;
const invariantBoundingSphere = Sphere3D.clone(cylinders.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('cylinders'),
@@ -245,6 +246,7 @@ export namespace Cylinders {
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
dSolidInterior: ValueCell.create(props.solidInterior),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
};
@@ -263,13 +265,14 @@ export namespace Cylinders {
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
}
function updateBoundingSphere(values: CylindersValues, cylinders: Cylinders) {
const invariantBoundingSphere = Sphere3D.clone(cylinders.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -223,7 +223,7 @@ export namespace DirectVolume {
const counts = { drawCount: VolumeBox.indices.length, vertexCount: x * y * z, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
const transferTex = createTransferFunctionTexture(controlPoints);
@@ -295,7 +295,7 @@ export namespace DirectVolume {
function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -154,7 +154,7 @@ namespace Image {
const counts = { drawCount: QuadIndices.length, vertexCount: QuadPositions.length / 3, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('image'),
@@ -199,7 +199,7 @@ namespace Image {
function updateBoundingSphere(values: ImageValues, image: Image) {
const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -219,7 +219,7 @@ export namespace Lines {
const counts = { drawCount: lines.lineCount * 2 * 3, vertexCount: lines.lineCount * 4, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(lines.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('lines'),
@@ -263,7 +263,7 @@ export namespace Lines {
function updateBoundingSphere(values: LinesValues, lines: Lines) {
const invariantBoundingSphere = Sphere3D.clone(lines.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 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>
@@ -680,7 +680,7 @@ export namespace Mesh {
const counts = { drawCount: mesh.triangleCount * 3, vertexCount: mesh.vertexCount, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(mesh.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('mesh'),
@@ -735,7 +735,7 @@ export namespace Mesh {
function updateBoundingSphere(values: MeshValues, mesh: Mesh) {
const invariantBoundingSphere = Sphere3D.clone(mesh.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -21,6 +21,7 @@ export type OverpaintData = {
uOverpaintGridDim: ValueCell<Vec3>,
uOverpaintGridTransform: ValueCell<Vec4>,
dOverpaintType: ValueCell<string>,
uOverpaintStrength: ValueCell<number>,
}
export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) {
@@ -54,6 +55,7 @@ export function createOverpaint(count: number, type: OverpaintType, overpaintDat
uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dOverpaintType: ValueCell.create(type),
uOverpaintStrength: ValueCell.create(1),
};
}
}
@@ -74,6 +76,7 @@ export function createEmptyOverpaint(overpaintData?: OverpaintData): OverpaintDa
uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dOverpaintType: ValueCell.create('groupInstance'),
uOverpaintStrength: ValueCell.create(1),
};
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -181,7 +181,7 @@ export namespace Points {
const counts = { drawCount: points.pointCount, vertexCount: points.pointCount, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(points.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('points'),
@@ -222,7 +222,7 @@ export namespace Points {
function updateBoundingSphere(values: PointsValues, points: Points) {
const invariantBoundingSphere = Sphere3D.clone(points.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -130,6 +130,7 @@ export namespace Spheres {
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
};
@@ -183,7 +184,7 @@ export namespace Spheres {
const padding = spheres.boundingSphere.radius ? getMaxSize(size) * props.sizeFactor : 0;
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('spheres'),
@@ -212,6 +213,7 @@ export namespace Spheres {
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
dSolidInterior: ValueCell.create(props.solidInterior),
uBumpFrequency: ValueCell.create(props.bumpFrequency),
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
};
@@ -230,6 +232,7 @@ export namespace Spheres {
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
}
@@ -239,7 +242,7 @@ export namespace Spheres {
? getMaxSize(values) * values.uSizeFactor.ref.value
: 0;
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -21,6 +21,7 @@ export type SubstanceData = {
uSubstanceGridDim: ValueCell<Vec3>,
uSubstanceGridTransform: ValueCell<Vec4>,
dSubstanceType: ValueCell<string>,
uSubstanceStrength: ValueCell<number>,
}
export function applySubstanceMaterial(array: Uint8Array, start: number, end: number, material: Material) {
@@ -54,6 +55,7 @@ export function createSubstance(count: number, type: SubstanceType, substanceDat
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create(type),
uSubstanceStrength: ValueCell.create(1),
};
}
}
@@ -74,6 +76,7 @@ export function createEmptySubstance(substanceData?: SubstanceData): SubstanceDa
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create('groupInstance'),
uSubstanceStrength: ValueCell.create(1),
};
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -223,7 +223,7 @@ export namespace Text {
const padding = getPadding(text.mappingBuffer.ref.value, text.depthBuffer.ref.value, text.charCount, getMaxSize(size));
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('text'),
@@ -290,7 +290,7 @@ export namespace Text {
function updateBoundingSphere(values: TextValues, text: Text) {
const padding = getPadding(values.aMapping.ref.value, values.aDepth.ref.value, text.charCount, getMaxSize(values));
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -7,7 +7,7 @@
import { ValueCell } from '../../../mol-util';
import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Texture } from '../../../mol-gl/webgl/texture';
import { isNullTexture, Texture } from '../../../mol-gl/webgl/texture';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, Values, DefineSpec } from '../../../mol-gl/renderable/schema';
@@ -267,7 +267,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
const [dx, dy, dz] = gridDim;
const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim);
// console.log({ width, height, texCols, dim, resolution });
// console.log({ width, height, texCols, gridDim, resolution });
if (!webgl.namedFramebuffers[ColorAccumulateName]) {
webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
@@ -363,7 +363,9 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
// normalize
if (isTimingMode) webgl.timer.mark('ColorNormalize.render');
if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
if (!texture || isNullTexture(texture)) {
texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
}
texture.define(width, height);
const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture, countTexture);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -7,7 +7,7 @@
import { ValueCell } from '../../../mol-util';
import { Sphere3D } from '../../../mol-math/geometry';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
import { TransformData } from '../transform-data';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
@@ -20,11 +20,12 @@ import { createEmptyTransparency } from '../transparency-data';
import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { createNullTexture, Texture } from '../../../mol-gl/webgl/texture';
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { createEmptySubstance } from '../substance-data';
import { RenderableState } from '../../../mol-gl/renderable';
import { WebGLContext } from '../../../mol-gl/webgl/context';
export interface TextureMesh {
readonly kind: 'texture-mesh',
@@ -43,7 +44,10 @@ export interface TextureMesh {
readonly boundingSphere: Sphere3D
readonly meta: { [k: string]: unknown }
readonly meta: {
webgl?: WebGLContext
[k: string]: unknown
}
}
export namespace TextureMesh {
@@ -131,9 +135,42 @@ export namespace TextureMesh {
updateBoundingSphere,
createRenderableState,
updateRenderableState,
createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
createPositionIterator,
};
const TextureMeshName = 'texture-mesh';
function createPositionIterator(textureMesh: TextureMesh, transform: TransformData): LocationIterator {
const webgl = textureMesh.meta.webgl;
if (!webgl) return LocationIterator(1, 1, 1, () => NullLocation);
if (!webgl.namedFramebuffers[TextureMeshName]) {
webgl.namedFramebuffers[TextureMeshName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[TextureMeshName];
const [width, height] = textureMesh.geoTextureDim.ref.value;
const vertices = new Float32Array(width * height * 4);
framebuffer.bind();
textureMesh.vertexTexture.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, vertices);
const groupCount = textureMesh.vertexCount;
const instanceCount = transform.instanceCount.ref.value;
const location = PositionLocation();
const p = location.position;
const v = vertices;
const m = transform.aTransform.ref.value;
const getLocation = (groupIndex: number, instanceIndex: number) => {
if (instanceIndex < 0) {
Vec3.fromArray(p, v, groupIndex * 4);
} else {
Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 4, instanceIndex * 16);
}
return location;
};
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
function createValues(textureMesh: TextureMesh, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextureMeshValues {
const { instanceCount, groupCount } = locationIt;
const positionIt = Utils.createPositionIterator(textureMesh, transform);
@@ -150,7 +187,7 @@ export namespace TextureMesh {
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
return {
dGeometryType: ValueCell.create('textureMesh'),
@@ -207,7 +244,7 @@ export namespace TextureMesh {
function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value, 0);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);

View File

@@ -21,6 +21,7 @@ export type TransparencyData = {
uTransparencyGridDim: ValueCell<Vec3>,
uTransparencyGridTransform: ValueCell<Vec4>,
dTransparencyType: ValueCell<string>,
uTransparencyStrength: ValueCell<number>,
}
export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) {
@@ -63,6 +64,7 @@ export function createTransparency(count: number, type: TransparencyType, transp
uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dTransparencyType: ValueCell.create(type),
uTransparencyStrength: ValueCell.create(1),
};
}
}
@@ -84,6 +86,7 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr
uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dTransparencyType: ValueCell.create('groupInstance'),
uTransparencyStrength: ValueCell.create(1),
};
}
}

View File

@@ -19,6 +19,10 @@ export class CommitQueue {
return this.removeList.count === 0 && this.addList.count === 0;
}
get size() {
return this.removeMap.size + this.addMap.size;
}
add(o: GraphicsRenderObject) {
if (this.removeMap.has(o)) {
const a = this.removeMap.get(o)!;

View File

@@ -204,8 +204,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
// return at least a count of one to avoid issues downstram
const count = Math.max(1, getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture));
const height = Math.ceil(count / Math.pow(2, levels));
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height);
// console.log('height', height, 'finalCount', count, 'scale', scale);
// console.log({ height, count, scale });
return { pyramidTex, count, height, levels, scale };
}

View File

@@ -142,9 +142,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
if (isWebGL2(gl)) {
if (!vertexTexture) {
vertexTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!groupTexture) {
@@ -199,9 +197,9 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
gl.finish();
if (isTimingMode) ctx.timer.markEnd('createIsosurfaceBuffers');
// printTextureImage(readTexture(ctx, vertexTexture, new Float32Array(width * height * 4)), { scale: 0.75 });
// printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75 });
// printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75 });
// printTextureImage(readTexture(ctx, vertexTexture, new Float32Array(width * height * 4)), { scale: 0.75, normalize: true });
// printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75, normalize: true });
// printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75, normalize: true });
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
}

View File

@@ -27,6 +27,7 @@ export const CylindersSchema = {
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
dSolidInterior: DefineSpec('boolean'),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -152,9 +152,12 @@ export const GlobalUniformSchema = {
uHighlightColor: UniformSpec('v3'),
uSelectColor: UniformSpec('v3'),
uDimColor: UniformSpec('v3'),
uHighlightStrength: UniformSpec('f'),
uSelectStrength: UniformSpec('f'),
uDimStrength: UniformSpec('f'),
uMarkerPriority: UniformSpec('i'),
uMarkerAverage: UniformSpec('f'),
uXrayEdgeFalloff: UniformSpec('f'),
@@ -229,6 +232,7 @@ export const OverpaintSchema = {
uOverpaintGridTransform: UniformSpec('v4'),
tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
dOverpaintType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uOverpaintStrength: UniformSpec('f', 'material'),
} as const;
export type OverpaintSchema = typeof OverpaintSchema
export type OverpaintValues = Values<OverpaintSchema>
@@ -242,7 +246,8 @@ export const TransparencySchema = {
uTransparencyGridDim: UniformSpec('v3'),
uTransparencyGridTransform: UniformSpec('v4'),
tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance'])
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uTransparencyStrength: UniformSpec('f', 'material'),
} as const;
export type TransparencySchema = typeof TransparencySchema
export type TransparencyValues = Values<TransparencySchema>
@@ -256,6 +261,7 @@ export const SubstanceSchema = {
uSubstanceGridTransform: UniformSpec('v4'),
tSubstanceGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
dSubstanceType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
uSubstanceStrength: UniformSpec('f', 'material'),
} as const;
export type SubstanceSchema = typeof SubstanceSchema
export type SubstanceValues = Values<SubstanceSchema>

View File

@@ -24,6 +24,7 @@ export const SpheresSchema = {
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
dSolidInterior: DefineSpec('boolean'),
uBumpFrequency: UniformSpec('f', 'material'),
uBumpAmplitude: UniformSpec('f', 'material'),
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,6 +10,10 @@ import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { TextureFilter } from '../webgl/texture';
import { arrayMinMax } from '../../mol-util/array';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4Offset = Vec3.transformMat4Offset;
export function calculateTextureInfo(n: number, itemSize: number) {
n = Math.max(n, 2); // observed issues with 1 pixel textures
const sqN = Math.sqrt(n);
@@ -137,21 +141,21 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
boundaryHelper.reset();
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i);
v3fromArray(v, position, i);
boundaryHelper.includePosition(v);
}
boundaryHelper.finishedIncludeStep();
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i);
v3fromArray(v, position, i);
boundaryHelper.radiusPosition(v);
}
const sphere = boundaryHelper.getSphere();
if (positionCount <= 98) {
if (positionCount <= 14) {
const extrema: Vec3[] = [];
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
extrema.push(Vec3.fromArray(Vec3(), position, i));
extrema.push(v3fromArray(Vec3(), position, i));
}
Sphere3D.setExtrema(sphere, extrema);
}
@@ -161,9 +165,9 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
const _mat4 = Mat4();
export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number): Sphere3D {
export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number, transformOffset: number): Sphere3D {
if (transformCount === 1) {
Mat4.fromArray(_mat4, transform, 0);
Mat4.fromArray(_mat4, transform, transformOffset);
const s = Sphere3D.clone(invariantBoundingSphere);
return Mat4.isIdentity(_mat4) ? s : Sphere3D.transform(s, s, _mat4);
}
@@ -174,28 +178,28 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
const { center, radius, extrema } = invariantBoundingSphere;
// only use extrema if there are not too many transforms
if (extrema && transformCount < 50) {
if (extrema && transformCount <= 14) {
for (let i = 0, _i = transformCount; i < _i; ++i) {
for (const e of extrema) {
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16);
v3transformMat4Offset(v, e, transform, 0, 0, i * 16 + transformOffset);
boundaryHelper.includePosition(v);
}
}
boundaryHelper.finishedIncludeStep();
for (let i = 0, _i = transformCount; i < _i; ++i) {
for (const e of extrema) {
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16);
v3transformMat4Offset(v, e, transform, 0, 0, i * 16 + transformOffset);
boundaryHelper.radiusPosition(v);
}
}
} else {
for (let i = 0, _i = transformCount; i < _i; ++i) {
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16);
v3transformMat4Offset(v, center, transform, 0, 0, i * 16 + transformOffset);
boundaryHelper.includePositionRadius(v, radius);
}
boundaryHelper.finishedIncludeStep();
for (let i = 0, _i = transformCount; i < _i; ++i) {
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16);
v3transformMat4Offset(v, center, transform, 0, 0, i * 16 + transformOffset);
boundaryHelper.radiusPositionRadius(v, radius);
}
}
@@ -205,7 +209,7 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {
const invariantBoundingSphere = calculateInvariantBoundingSphere(position, positionCount, stepFactor);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform, transformCount);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform, transformCount, 0);
Sphere3D.expand(boundingSphere, boundingSphere, padding);
Sphere3D.expand(invariantBoundingSphere, invariantBoundingSphere, padding);
return { boundingSphere, invariantBoundingSphere };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -38,14 +38,14 @@ export interface RendererStats {
instancedDrawCount: number
}
export const enum PickType {
export enum PickType {
None = 0,
Object = 1,
Instance = 2,
Group = 3,
}
export const enum MarkingType {
export enum MarkingType {
None = 0,
Depth = 1,
Mask = 2,
@@ -54,10 +54,11 @@ export const enum MarkingType {
interface Renderer {
readonly stats: RendererStats
readonly props: Readonly<RendererProps>
readonly light: Readonly<Light>
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => void
clearDepth: (packed?: boolean) => void
update: (camera: ICamera) => void
update: (camera: ICamera, scene: Scene) => void
renderPick: (group: Scene.Group, camera: ICamera, variant: 'pick' | 'depth', depthTexture: Texture | null, pickType: PickType) => void
renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
@@ -96,29 +97,31 @@ export const RendererParams = {
colorMarker: PD.Boolean(true, { description: 'Enable color marker' }),
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
dimColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
highlightStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
selectStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
dimStrength: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.1 }),
markerPriority: PD.Select(1, [[1, 'Highlight'], [2, 'Select']]),
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
light: PD.ObjectList({
inclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
azimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
inclination: PD.Numeric(150, { min: 0, max: 180, step: 1 }),
azimuth: PD.Numeric(320, { min: 0, max: 360, step: 1 }),
color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
intensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
intensity: PD.Numeric(0.6, { min: 0.0, max: 5.0, step: 0.01 }),
}, o => Color.toHexString(o.color), { defaultValue: [{
inclination: 180,
azimuth: 0,
inclination: 150,
azimuth: 320,
color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
intensity: 0.6
}] }),
ambientColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 2.0, step: 0.01 }),
};
export type RendererProps = PD.Values<typeof RendererParams>
type Light = {
export type Light = {
count: number
direction: number[]
color: number[]
@@ -231,9 +234,12 @@ namespace Renderer {
uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
uDimColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.dimColor)),
uHighlightStrength: ValueCell.create(p.highlightStrength),
uSelectStrength: ValueCell.create(p.selectStrength),
uDimStrength: ValueCell.create(p.dimStrength),
uMarkerPriority: ValueCell.create(p.markerPriority),
uMarkerAverage: ValueCell.create(0),
uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff),
};
@@ -328,7 +334,7 @@ namespace Renderer {
r.render(variant, sharedTexturesList.length);
};
const update = (camera: ICamera) => {
const update = (camera: ICamera, scene: Scene) => {
ValueCell.update(globalUniforms.uView, camera.view);
ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view));
ValueCell.update(globalUniforms.uProjection, camera.projection);
@@ -345,13 +351,15 @@ namespace Renderer {
ValueCell.updateIfChanged(globalUniforms.uFogFar, camera.fogFar);
ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear);
ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
ValueCell.updateIfChanged(globalUniforms.uMarkerAverage, scene.markerAverage);
};
const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderMask: Mask, markingDepthTest: boolean) => {
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
ValueCell.update(globalUniforms.uModel, group.view);
ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view));
ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, camera.view, group.view));
ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView));
ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection));
ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
@@ -754,6 +762,10 @@ namespace Renderer {
p.selectColor = props.selectColor;
ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
}
if (props.dimColor !== undefined && props.dimColor !== p.dimColor) {
p.dimColor = props.dimColor;
ValueCell.update(globalUniforms.uDimColor, Color.toVec3Normalized(globalUniforms.uDimColor.ref.value, p.dimColor));
}
if (props.highlightStrength !== undefined && props.highlightStrength !== p.highlightStrength) {
p.highlightStrength = props.highlightStrength;
ValueCell.update(globalUniforms.uHighlightStrength, p.highlightStrength);
@@ -762,6 +774,10 @@ namespace Renderer {
p.selectStrength = props.selectStrength;
ValueCell.update(globalUniforms.uSelectStrength, p.selectStrength);
}
if (props.dimStrength !== undefined && props.dimStrength !== p.dimStrength) {
p.dimStrength = props.dimStrength;
ValueCell.update(globalUniforms.uDimStrength, p.dimStrength);
}
if (props.markerPriority !== undefined && props.markerPriority !== p.markerPriority) {
p.markerPriority = props.markerPriority;
ValueCell.update(globalUniforms.uMarkerPriority, p.markerPriority);
@@ -827,6 +843,9 @@ namespace Renderer {
instancedDrawCount: stats.instancedDrawCount,
};
},
get light(): Light {
return light;
},
dispose: () => {
// TODO
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 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>
@@ -77,6 +77,7 @@ interface Scene extends Object3D {
remove: (o: GraphicsRenderObject) => void
commit: (maxTimeMs?: number) => boolean
readonly needsCommit: boolean
readonly commitQueueSize: number
has: (o: GraphicsRenderObject) => boolean
clear: () => void
forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
@@ -105,6 +106,10 @@ namespace Scene {
let boundingSphereDirty = true;
let boundingSphereVisibleDirty = true;
let markerAverageDirty = true;
let opacityAverageDirty = true;
let hasOpaqueDirty = true;
let markerAverage = 0;
let opacityAverage = 0;
let hasOpaque = false;
@@ -165,9 +170,9 @@ namespace Scene {
}
renderables.sort(renderableSort);
markerAverage = calculateMarkerAverage();
opacityAverage = calculateOpacityAverage();
hasOpaque = calculateHasOpaque();
markerAverageDirty = true;
opacityAverageDirty = true;
hasOpaqueDirty = true;
return true;
}
@@ -189,9 +194,9 @@ namespace Scene {
const newVisibleHash = computeVisibleHash();
if (newVisibleHash !== visibleHash) {
boundingSphereVisibleDirty = true;
markerAverage = calculateMarkerAverage();
opacityAverage = calculateOpacityAverage();
hasOpaque = calculateHasOpaque();
markerAverageDirty = true;
opacityAverageDirty = true;
hasOpaqueDirty = true;
visibleHash = newVisibleHash;
return true;
} else {
@@ -268,13 +273,14 @@ namespace Scene {
} else {
syncVisibility();
}
markerAverage = calculateMarkerAverage();
opacityAverage = calculateOpacityAverage();
hasOpaque = calculateHasOpaque();
markerAverageDirty = true;
opacityAverageDirty = true;
hasOpaqueDirty = true;
},
add: (o: GraphicsRenderObject) => commitQueue.add(o),
remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
commit: (maxTime = Number.MAX_VALUE) => commit(maxTime),
get commitQueueSize() { return commitQueue.size; },
get needsCommit() { return !commitQueue.isEmpty; },
has: (o: GraphicsRenderObject) => {
return renderableMap.has(o);
@@ -311,12 +317,24 @@ namespace Scene {
return boundingSphereVisible;
},
get markerAverage() {
if (markerAverageDirty) {
markerAverage = calculateMarkerAverage();
markerAverageDirty = false;
}
return markerAverage;
},
get opacityAverage() {
if (opacityAverageDirty) {
opacityAverage = calculateOpacityAverage();
opacityAverageDirty = false;
}
return opacityAverage;
},
get hasOpaque() {
if (hasOpaqueDirty) {
hasOpaque = calculateHasOpaque();
hasOpaqueDirty = false;
}
return hasOpaque;
},
};

View File

@@ -12,7 +12,7 @@ export const apply_light_color = `
gl_FragColor = material;
#else
#ifdef bumpEnabled
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) {
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0 && bumpiness > 0.0) {
normal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency);
}
#endif

View File

@@ -1,4 +1,5 @@
export const apply_marker_color = `
#if defined(dColorMarker)
if (marker > 0.0) {
if ((uMarkerPriority == 1 && marker != 2.0) || (uMarkerPriority != 1 && marker == 1.0)) {
@@ -8,6 +9,9 @@ export const apply_marker_color = `
gl_FragColor.rgb = mix(gl_FragColor.rgb, uSelectColor, uSelectStrength);
gl_FragColor.a = max(gl_FragColor.a, uSelectStrength * 0.002); // for direct-volume rendering
}
} else if (uMarkerAverage > 0.0) {
gl_FragColor.rgb = mix(gl_FragColor.rgb, uDimColor, uDimStrength);
gl_FragColor.a = max(gl_FragColor.a, uDimStrength * 0.002); // for direct-volume rendering
}
#endif
`;

View File

@@ -42,6 +42,7 @@ export const assign_color_varying = `
#else
vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
vOverpaint *= uOverpaintStrength;
#endif
#ifdef dSubstance
@@ -58,6 +59,7 @@ export const assign_color_varying = `
// pre-mix to avoid artifacts due to empty substance
vSubstance.rgb = mix(vec3(uMetalness, uRoughness, uBumpiness), vSubstance.rgb, vSubstance.a);
vSubstance *= uSubstanceStrength;
#endif
#elif defined(dRenderVariant_pick)
#ifdef requiredDrawBuffers
@@ -86,5 +88,6 @@ export const assign_color_varying = `
vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;
#endif
vTransparency *= uTransparencyStrength;
#endif
`;

View File

@@ -1,12 +1,7 @@
export const clip_instance = `
#if defined(dClipVariant_instance) && dClipObjectCount != 0
int flag = 0;
#if defined(dClipping)
flag = int(floor(vClipping * 255.0 + 0.5));
#endif
vec4 mCenter = uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0);
if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w), flag))
if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w)))
// move out of [ -w, +w ] to 'discard' in vert shader
gl_Position.z = 2.0 * gl_Position.w;
#endif

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