mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 05:44:23 +08:00
Compare commits
1 Commits
v3.33.0
...
safari-deb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db0ada684 |
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"brace-style": "off",
|
||||
"comma-spacing": "off",
|
||||
"space-infix-ops": "off",
|
||||
"space-infix-ops": "error",
|
||||
"comma-dangle": "off",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
@@ -107,7 +107,6 @@
|
||||
"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",
|
||||
|
||||
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@@ -1,10 +0,0 @@
|
||||
<!-- Thank you for contributing to Mol* -->
|
||||
|
||||
# Description
|
||||
|
||||
|
||||
## Actions
|
||||
|
||||
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
|
||||
- [ ] Updated headers of modified files
|
||||
- [ ] Added my name to `package.json`'s `contributors`
|
||||
4
.github/workflows/node.yml
vendored
4
.github/workflows/node.yml
vendored
@@ -1,5 +1,3 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
@@ -17,6 +15,6 @@ jobs:
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Test
|
||||
run: npm install --no-save "gl@^6.0.2" && xvfb-run --auto-servernum npm run jest
|
||||
run: npm install --no-save "gl@^5.0.0" && xvfb-run --auto-servernum npm run jest
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
18
.travis.yml
Normal file
18
.travis.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -6,6 +6,7 @@
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"firsttris.vscode-jest-runner",
|
||||
"msjsdiag.debugger-for-chrome",
|
||||
"slevesque.shader",
|
||||
"stpn.vscode-graphql",
|
||||
"wayou.vscode-todo-highlight"
|
||||
|
||||
225
CHANGELOG.md
225
CHANGELOG.md
@@ -6,231 +6,6 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.33.0] - 2023-04-02
|
||||
|
||||
- Handle resizes of viewer element even when window remains the same size
|
||||
- Throttle canvas resize events
|
||||
- Selection toggle buttons hidden if selection mode is off
|
||||
- Camera focus loci bindings allow reset on click-away to be overridden
|
||||
- Input/controls improvements
|
||||
- Move or fly around the scene using keys
|
||||
- Pointer lock to look around scene
|
||||
- Toggle spin/rock animation using keys
|
||||
- Apply bumpiness as lightness variation with `ignoreLight`
|
||||
- Remove `JSX` reference from `loci-labels.ts`
|
||||
- Fix overpaint/transparency/substance smoothing not updated when geometry changes
|
||||
- Fix camera project/unproject when using offset viewport
|
||||
- Add support for loading all blocks from a mmcif file as a trajectory
|
||||
- Add `Frustum3D` and `Plane3D` math primitives
|
||||
- Include `occupancy` and `B_iso_or_equiv` when creating `Conformation` from `Model`
|
||||
- Remove LazyImports (introduced in v3.31.1)
|
||||
|
||||
## [v3.32.0] - 2023-03-20
|
||||
|
||||
- Avoid rendering of fully transparent renderables
|
||||
- Add occlusion color parameter
|
||||
- Fix issue with outlines and orthographic camera
|
||||
- Reduce over-blurring occlusion at larger view distances
|
||||
- Fix occlusion artefact with non-canvas viewport and pixel-ratio > 1
|
||||
- Update nodejs-shims conditionals to handle polyfilled document object in NodeJS environment.
|
||||
- Ensure marking edges are at least one pixel wide
|
||||
- Add exposure parameter to renderer
|
||||
- Only trigger marking when mouse is directly over canvas
|
||||
- Fix blurry occlusion in screenshots
|
||||
- [Breaking] Add `setFSModule` to `mol-util/data-source` instead of trying to trick WebPack
|
||||
|
||||
## [v3.31.4] - 2023-02-24
|
||||
|
||||
- Allow link cylinder/line `dashCount` set to '0'
|
||||
- Stop animation loop when disposing `PluginContext` (thanks @gfrn for identifying the issue)
|
||||
|
||||
## [v3.31.3] - 2023-02-22
|
||||
|
||||
- Fix impostor bond visuals not correctly updating on `sizeFactor` changes
|
||||
- Fix degenerate case in PCA
|
||||
- Fix near clipping avoidance in impostor shaders
|
||||
- Update `fs` import in `data-source.ts`
|
||||
|
||||
## [v3.31.2] - 2023-02-12
|
||||
|
||||
- Fix exit code of volume pack executable (pack.ts). Now exits with non-0 status when an error happens
|
||||
- Remove pca transform from components ui focus (too distracting)
|
||||
- Fix artefacts with opaque outlines behind transparent objects
|
||||
- Fix polymer trace visual not updating
|
||||
- Fix use of `WEBGL_provoking_vertex`
|
||||
|
||||
## [v3.31.1] - 2023-02-05
|
||||
|
||||
- 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 projection
|
||||
- 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 `Whole Structure` to `Auto`
|
||||
|
||||
## [v3.22.0] - 2022-10-17
|
||||
|
||||
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
|
||||
|
||||
## [v3.21.0] - 2022-10-17
|
||||
|
||||
- Add `VolumeIsosurfaceParams.pickingGranularity` param
|
||||
- Prevent component controls collapsing when option is selected
|
||||
|
||||
## [v3.20.0] - 2022-10-16
|
||||
|
||||
- [Breaking] Rename the ``model-index`` color theme to ``trajectory-index``
|
||||
- Add a new ``model-index`` color theme that uniquely colors each loaded model
|
||||
- Add the new ``model-index`` and ``structure-index`` color themes as an option for the carbon color in the ``element-symbol`` and ``ilustrative`` color themes
|
||||
- Add ``structure-index`` color theme that uniquely colors each root structure
|
||||
- Add ``nearest`` method to ``Lookup3D``
|
||||
- Add mipmap-based blur for skybox backgrounds
|
||||
|
||||
## [v3.19.0] - 2022-10-01
|
||||
|
||||
- Fix "empty textures" error on empty canvas
|
||||
- Optimize BinaryCIF integer packing encoder
|
||||
- Fix dual depth peeling when post-processing is off or when rendering direct-volumes
|
||||
- Add ``cameraClipping.minNear`` parameter
|
||||
- Fix black artifacts on specular highlights with transparent background
|
||||
|
||||
## [v3.18.0] - 2022-09-17
|
||||
|
||||
- Integration of Dual depth peeling - OIT method
|
||||
- Stereo camera improvements
|
||||
- Fix param updates not applied
|
||||
- Better param ranges and description
|
||||
- Add timer.mark for left/right camera
|
||||
|
||||
## [v3.17.0] - 2022-09-11
|
||||
|
||||
- [Fix] Clone ``Canvas3DParams`` when creating a ``Canvas3D`` instance to prevent shared state between multiple instances
|
||||
- Add ``includeResidueTest`` option to ``alignAndSuperposeWithSIFTSMapping``
|
||||
- Add ``parentDisplay`` param for interactions representation.
|
||||
- [Experimental] Add support for PyMOL, VMD, and Jmol atom expressions in selection scripts
|
||||
- Support for ``failIfMajorPerformanceCaveat`` webgl attribute. Add ``PluginConfig.General.AllowMajorPerformanceCaveat`` and ``allow-major-performance-caveat`` Viewer GET param.
|
||||
- Fix handling of PDB TER records (#549)
|
||||
- Add support for getting multiple loci from a representation (``.getAllLoci()``)
|
||||
- Add ``key`` property to intra- and inter-bonds for referencing source data
|
||||
- Fix click event triggered after move
|
||||
|
||||
## [v3.16.0] - 2022-08-25
|
||||
|
||||
- Support ``globalColorParams`` and ``globalSymmetryParams`` in common representation params
|
||||
- Support ``label`` parameter in ``Viewer.loadStructureFromUrl``
|
||||
- Fix ``ViewportHelpContent`` Mouse Controls section
|
||||
|
||||
## [v3.15.0] - 2022-08-23
|
||||
|
||||
- Fix wboit in Safari >=15 (add missing depth renderbuffer to wboit pass)
|
||||
- Add 'Around Camera' option to Volume streaming
|
||||
- Avoid queuing more than one update in Volume streaming
|
||||
|
||||
## [v3.14.0] - 2022-08-20
|
||||
|
||||
- Expose inter-bonds compute params in structure
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[](./LICENSE)
|
||||
[](https://www.npmjs.com/package/molstar)
|
||||
[](https://github.com/molstar/molstar/actions/workflows/node.yml)
|
||||
[](https://travis-ci.org/molstar/molstar)
|
||||
[](https://gitter.im/molstar/Lobby)
|
||||
|
||||
# Mol*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
9584
package-lock.json
generated
9584
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
101
package.json
101
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.33.0",
|
||||
"version": "3.14.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@^6.0.2\" && npm run lint && jest",
|
||||
"test": "npm install --no-save \"gl@^5.0.0\" && npm run lint && jest",
|
||||
"jest": "jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
@@ -89,91 +89,76 @@
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
|
||||
"Adam Midlik <midlik@gmail.com>",
|
||||
"Koya Sakuma <koya.sakuma.work@gmail.com>",
|
||||
"Gianluca Tomasello <giagitom@gmail.com>",
|
||||
"Ke Ma <mark.ma@rcsb.org>",
|
||||
"Jason Pattle <jpattle@exscientia.co.uk>",
|
||||
"David Williams <dwilliams@nobiastx.com>",
|
||||
"Zhenyu Zhang <jump2cn@gmail.com>",
|
||||
"Russell Parker <russell@benchling.com>"
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^4.0.1",
|
||||
"@graphql-codegen/cli": "^3.2.2",
|
||||
"@graphql-codegen/time": "^4.0.0",
|
||||
"@graphql-codegen/typescript": "^3.0.2",
|
||||
"@graphql-codegen/add": "^3.2.1",
|
||||
"@graphql-codegen/cli": "^2.11.6",
|
||||
"@graphql-codegen/time": "^3.2.1",
|
||||
"@graphql-codegen/typescript": "^2.7.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.9",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.2",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/gl": "^6.0.2",
|
||||
"@types/jpeg-js": "^0.3.7",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/react": "^18.0.32",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
"@typescript-eslint/parser": "^5.57.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.3",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.3",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.1",
|
||||
"@types/jest": "^28.1.7",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^8.0.1",
|
||||
"cpx2": "^4.2.2",
|
||||
"concurrently": "^7.3.0",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"eslint": "^8.37.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.22.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^29.5.0",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"jest": "^28.1.3",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.60.0",
|
||||
"sass-loader": "^13.2.2",
|
||||
"simple-git": "^3.17.0",
|
||||
"sass": "^1.54.5",
|
||||
"sass-loader": "^13.0.2",
|
||||
"simple-git": "^3.12.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.2",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.3",
|
||||
"webpack": "^5.77.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^28.0.8",
|
||||
"typescript": "^4.7.4",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.2",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/node": "^16.18.23",
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.51",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"body-parser": "^1.20.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"express": "^4.18.1",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.21",
|
||||
"immutable": "^4.3.0",
|
||||
"node-fetch": "^2.6.9",
|
||||
"rxjs": "^7.8.0",
|
||||
"swagger-ui-dist": "^4.18.2",
|
||||
"tslib": "^2.5.0",
|
||||
"immer": "^9.0.15",
|
||||
"immutable": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.6",
|
||||
"swagger-ui-dist": "^4.14.0",
|
||||
"tslib": "^2.4.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,7 +15,7 @@ const deployDir = path.resolve(buildDir, 'deploy/');
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/');
|
||||
|
||||
const analyticsTag = /<!-- __MOLSTAR_ANALYTICS__ -->/g;
|
||||
const analyticsCode = `<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "c414cbae2d284ea995171a81e4a3e721"}'></script><!-- End Cloudflare Web Analytics --><script defer src="https://web3dsurvey.com/collector.js"></script>`;
|
||||
const analyticsCode = `<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "c414cbae2d284ea995171a81e4a3e721"}'></script><!-- End Cloudflare Web Analytics -->`;
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
|
||||
@@ -58,22 +58,20 @@ class Viewer {
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = {
|
||||
...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
const o = { ...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowControls: false,
|
||||
viewportShowSettings: false,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
}
|
||||
};
|
||||
viewportShowExpand: true,
|
||||
viewportShowControls: false,
|
||||
viewportShowSettings: false,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
} };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
@@ -137,16 +135,18 @@ class Viewer {
|
||||
}
|
||||
};
|
||||
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, {
|
||||
settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
if (v) {
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
} });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ class Viewer {
|
||||
structures.push({ ref: structureProperties?.ref || structure.ref });
|
||||
}
|
||||
|
||||
// remove current structures from hierarchy as they will be merged
|
||||
// remove current structuresfrom hierarchy as they will be merged
|
||||
// TODO only works with using loadStructuresFromUrlsAndMerge once
|
||||
// need some more API metho to work with the hierarchy
|
||||
this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -31,8 +31,7 @@ function shinyStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'off', params: {} },
|
||||
shadow: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} }
|
||||
}
|
||||
} });
|
||||
}
|
||||
@@ -45,21 +44,17 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
blurKernelSize: 15,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
radius: 5,
|
||||
samples: 32,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
resolutionScale: 1
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
threshold: 0.33,
|
||||
color: Color(0x0000),
|
||||
includeTransparent: true,
|
||||
} },
|
||||
shadow: { name: 'off', params: {} },
|
||||
} }
|
||||
}
|
||||
} });
|
||||
}
|
||||
@@ -207,14 +202,14 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
|
||||
const components = {
|
||||
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
|
||||
interactions: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
|
||||
};
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, parentDisplay: 'between' }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -7,9 +7,8 @@
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoNtCs } from '../../extensions/dnatco';
|
||||
import { DnatcoConfalPyramids } 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';
|
||||
@@ -50,17 +49,16 @@ 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, consoleStats } from '../../mol-util/debug';
|
||||
export { setDebugMode, setProductionMode, setTimingMode } 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-ntcs': PluginSpec.Behavior(DnatcoNtCs),
|
||||
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
@@ -90,10 +88,7 @@ const DefaultViewerOptions = {
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
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,
|
||||
@@ -107,7 +102,6 @@ const DefaultViewerOptions = {
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
@@ -164,10 +158,7 @@ export class Viewer {
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[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],
|
||||
@@ -182,7 +173,6 @@ export class Viewer {
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
|
||||
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
|
||||
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
|
||||
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
|
||||
]
|
||||
};
|
||||
|
||||
@@ -209,7 +199,7 @@ export class Viewer {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions & { label?: string }) {
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
@@ -218,7 +208,6 @@ export class Viewer {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
label: options?.label,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
@@ -507,4 +496,4 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -38,15 +38,6 @@
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
// viewer.loadStructureFromUrl('my url', 'pdb', false, {
|
||||
// representationParams: {
|
||||
// theme: {
|
||||
// globalName: 'uniform',
|
||||
// globalColorParams: { value: 0xff0000 }
|
||||
// }
|
||||
// },
|
||||
// label: 'my structure'
|
||||
// });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -60,10 +60,7 @@
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
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,
|
||||
@@ -77,11 +74,8 @@
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
enableWboit: (disableWboit || enableDpoit) ? false : void 0, // use default value if disable-wboit is not set
|
||||
enableDpoit: enableDpoit ? true : void 0,
|
||||
enableWboit: disableWboit ? false : void 0, // use default value if disable-wboit is not set
|
||||
preferWebgl1: preferWebgl1,
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
powerPreference: powerPreference || 'high-performance',
|
||||
}).then(viewer => {
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
@@ -71,7 +71,6 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'ec-type':
|
||||
case 'ucode-alphanum-csv':
|
||||
case 'id_list':
|
||||
case 'entity_id_list':
|
||||
return ListCol('str', ',', description);
|
||||
case 'id_list_spc':
|
||||
return ListCol('str', ' ', description);
|
||||
@@ -152,7 +151,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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -38,7 +38,7 @@ function print(volume: Volume) {
|
||||
}
|
||||
|
||||
async function doMesh(volume: Volume, filename: string) {
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
|
||||
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
|
||||
|
||||
// Export the mesh in OBJ format.
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { AlphaOrbitalsExample } from '.';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
|
||||
|
||||
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
|
||||
createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
|
||||
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
|
||||
<Controls orbitals={orbitals} />
|
||||
</PluginContextContainer>);
|
||||
</PluginContextContainer>, parent);
|
||||
}
|
||||
|
||||
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
import { setDebugMode, setTimingMode, consoleStats } from '../../mol-util/debug';
|
||||
import { setDebugMode, setTimingMode } from '../../mol-util/debug';
|
||||
|
||||
interface DemoInput {
|
||||
moleculeSdf: string,
|
||||
@@ -82,20 +82,24 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
|
||||
if (!init) return;
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
});
|
||||
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
@@ -222,5 +226,4 @@ 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.consoleStats = consoleStats;
|
||||
(window as any).AlphaOrbitalsExample.setTimingMode = setTimingMode;
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* 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 gl from 'gl';
|
||||
import pngjs from 'pngjs';
|
||||
import jpegjs from 'jpeg-js';
|
||||
|
||||
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 { ExternalModules, STYLIZED_POSTPROCESSING } from '../../mol-plugin/util/headless-screenshot';
|
||||
import { setFSModule } from '../../mol-util/data-source';
|
||||
|
||||
|
||||
setFSModule(fs);
|
||||
|
||||
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 externalModules: ExternalModules = { gl, pngjs, 'jpeg-js': jpegjs };
|
||||
const plugin = new HeadlessPluginContext(externalModules, 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();
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -24,31 +24,8 @@ const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.33,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: true,
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
},
|
||||
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) } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1.0,
|
||||
@@ -59,25 +36,8 @@ const Canvas3DPresets = {
|
||||
occlusion: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
resolutionScale: 1,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
},
|
||||
shadow: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
},
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
@@ -90,8 +50,7 @@ const Canvas3DPresets = {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
shadow: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
|
||||
@@ -72,7 +72,6 @@ export const Backgrounds = PluginBehavior.create<{ }>({
|
||||
lightness: 0,
|
||||
saturation: 0,
|
||||
opacity: 1,
|
||||
blur: 0.3,
|
||||
}
|
||||
}
|
||||
}, 'Purple Nebula Skybox'],
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface Compartment {
|
||||
}
|
||||
|
||||
// Primitives discribing a compartment
|
||||
export enum CompartmentPrimitiveType {
|
||||
export const enum CompartmentPrimitiveType {
|
||||
MetaBall = 0,
|
||||
Sphere = 1,
|
||||
Cube = 2,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 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>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
@@ -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,
|
||||
hydrogens: 'hide-all',
|
||||
showHydrogens: false,
|
||||
});
|
||||
ctx.canvas3d?.setProps({
|
||||
multiSample: { mode: 'off' },
|
||||
@@ -600,21 +600,10 @@ export const LoadCellPackModel = StateAction.build({
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 8,
|
||||
bias: 1,
|
||||
blurKernelSize: 15,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.6,
|
||||
maxDistance: 80,
|
||||
steps: 3,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
@@ -623,7 +612,6 @@ export const LoadCellPackModel = StateAction.build({
|
||||
scale: 1,
|
||||
threshold: 0.33,
|
||||
color: ColorNames.black,
|
||||
includeTransparent: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* 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)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
|
||||
@@ -6,22 +6,23 @@
|
||||
*/
|
||||
|
||||
import { ConfalPyramidsColorThemeProvider } from './color';
|
||||
import { ConfalPyramidsProvider } from './property';
|
||||
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
|
||||
import { ConfalPyramidsRepresentationProvider } from './representation';
|
||||
import { Dnatco } from '../property';
|
||||
import { DnatcoTypes } from '../types';
|
||||
import { ConfalPyramidsTypes } from './types';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
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 ConfalPyramidsPreset = StructureRepresentationPresetProvider({
|
||||
export const DnatcoConfalPyramidsPreset = 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 => Dnatco.isApplicable(m));
|
||||
return a.data.models.length >= 1 && a.data.models.some(m => ConfalPyramids.isApplicable(m));
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
@@ -47,7 +48,50 @@ export const ConfalPyramidsPreset = StructureRepresentationPresetProvider({
|
||||
}
|
||||
});
|
||||
|
||||
export function confalPyramidLabel(step: DnatcoTypes.Step) {
|
||||
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;
|
||||
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})` : ''}
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
import { ErrorColor, NtCColors } from '../color';
|
||||
import { ConfalPyramidsProvider } from './property';
|
||||
import { ConfalPyramids, 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';
|
||||
@@ -21,7 +19,215 @@ import { ObjectKeys } from '../../../mol-util/type-helpers';
|
||||
|
||||
const Description = 'Assigns colors to confal pyramids';
|
||||
|
||||
const PyramidsColors = ColorMap({ ...NtCColors });
|
||||
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,
|
||||
});
|
||||
type PyramidsColors = typeof PyramidsColors;
|
||||
|
||||
export const ConfalPyramidsColorThemeParams = {
|
||||
@@ -66,7 +272,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 => Dnatco.isApplicable(m)),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && ConfalPyramidsProvider.ref(data.structure.models[0], false)
|
||||
|
||||
@@ -5,18 +5,90 @@
|
||||
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
import { Dnatco, DnatcoParams, DnatcoSteps } from '../property';
|
||||
import { ConfalPyramidsTypes as CPT } from './types';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { toTable } from '../../../mol-io/reader/cif/schema';
|
||||
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 const ConfalPyramidsParams = { ...DnatcoParams };
|
||||
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 type ConfalPyramidsParams = typeof ConfalPyramidsParams;
|
||||
export type ConfalPyramidsProps = PD.Values<ConfalPyramidsParams>;
|
||||
|
||||
export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramidsParams, DnatcoSteps> = CustomModelProperty.createProvider({
|
||||
export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramidsParams, ConfalPyramids> = CustomModelProperty.createProvider({
|
||||
label: 'Confal Pyramids',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'confal_pyramids',
|
||||
@@ -24,9 +96,94 @@ export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramids
|
||||
type: 'static',
|
||||
defaultParams: ConfalPyramidsParams,
|
||||
getParams: (data: Model) => ConfalPyramidsParams,
|
||||
isApplicable: (data: Model) => Dnatco.isApplicable(data),
|
||||
isApplicable: (data: Model) => ConfalPyramids.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ConfalPyramidsProps>) => {
|
||||
const p = { ...PD.getDefaultValues(ConfalPyramidsParams), ...props };
|
||||
return Dnatco.fromCif(ctx, data, p);
|
||||
return ConfalPyramids.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');
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
import { ConfalPyramidsProvider } from './property';
|
||||
import { ConfalPyramids, 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';
|
||||
@@ -88,8 +87,6 @@ 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;
|
||||
@@ -153,7 +150,9 @@ 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
|
||||
return CPT.Loci(data.steps, [idx]);
|
||||
const step = data.steps[idx];
|
||||
|
||||
return CPT.Loci({ step, isLower: groupId % 2 === 1 }, [{}]);
|
||||
}
|
||||
|
||||
function eachConfalPyramid(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
|
||||
@@ -198,7 +197,7 @@ export const ConfalPyramidsRepresentationProvider = StructureRepresentationProvi
|
||||
defaultValues: PD.getDefaultValues(ConfalPyramidsParams),
|
||||
defaultColorTheme: { name: 'confal-pyramids' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.models.some(m => Dnatco.isApplicable(m)),
|
||||
isApplicable: (structure: Structure) => structure.models.some(m => ConfalPyramids.isApplicable(m)),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => ConfalPyramidsProvider.attach(ctx, structure.model, void 0, true),
|
||||
detach: (data) => ConfalPyramidsProvider.ref(data.model, false),
|
||||
|
||||
@@ -5,29 +5,61 @@
|
||||
* @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 interface Location extends DataLocation<DnatcoTypes.HalfStep, {}> {}
|
||||
export const DataTag = 'dnatco-confal-half-pyramid';
|
||||
|
||||
export function Location(step: DnatcoTypes.Step, isLower: boolean) {
|
||||
return DataLocation(DnatcoTypes.DataTag, { step, isLower }, {});
|
||||
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 isLocation(x: any): x is Location {
|
||||
return !!x && x.kind === 'data-location' && x.tag === DnatcoTypes.DataTag;
|
||||
return !!x && x.kind === 'data-location' && x.tag === DataTag;
|
||||
}
|
||||
|
||||
export interface Loci extends DataLoci<DnatcoTypes.Step[], number> {}
|
||||
export interface Loci extends DataLoci<HalfPyramid, {}> {}
|
||||
|
||||
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 Loci(data: HalfPyramid, elements: ReadonlyArray<{}>): Loci {
|
||||
return DataLoci(DataTag, data, elements, undefined, () => confalPyramidLabel(data));
|
||||
}
|
||||
|
||||
export function isLoci(x: any): x is Loci {
|
||||
return !!x && x.kind === 'data-loci' && x.tag === DnatcoTypes.DataTag;
|
||||
return !!x && x.kind === 'data-loci' && x.tag === DataTag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Michal Malý <michal.maly@ibt.cas.cz>
|
||||
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
import { ConfalPyramidsProvider } from './property';
|
||||
import { DnatcoTypes } from '../types';
|
||||
import { DnatcoUtil } from '../util';
|
||||
import { ConfalPyramidsTypes as CPT } from './types';
|
||||
import { Segmentation } from '../../../mol-data/int';
|
||||
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure';
|
||||
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
|
||||
|
||||
type Residue = Segmentation.Segment<ResidueIndex>;
|
||||
|
||||
export type Pyramid = {
|
||||
O3: ElementIndex,
|
||||
@@ -21,17 +22,31 @@ export type Pyramid = {
|
||||
stepIdx: number,
|
||||
};
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
return { O3, P, OP1, OP2, O5, confalScore, stepIdx };
|
||||
}
|
||||
@@ -39,29 +54,39 @@ function getPyramid(
|
||||
export class ConfalPyramidsIterator {
|
||||
private chainIt: Segmentation.SegmentIterator<ChainIndex>;
|
||||
private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
|
||||
private residueOne?: DnatcoUtil.Residue;
|
||||
private residueTwo: DnatcoUtil.Residue;
|
||||
private data?: DnatcoTypes.Steps;
|
||||
private residueOne?: Residue;
|
||||
private residueTwo: Residue;
|
||||
private data?: CPT.Steps;
|
||||
private loc: StructureElement.Location;
|
||||
|
||||
private moveStep() {
|
||||
this.residueOne = DnatcoUtil.copyResidue(this.residueTwo);
|
||||
this.residueTwo = DnatcoUtil.copyResidue(this.residueIt.move())!;
|
||||
private getStepIndices(r: Residue) {
|
||||
this.loc.element = this.loc.unit.elements[r.start];
|
||||
|
||||
// Check for discontinuity
|
||||
if (this.residueTwo.index !== (this.residueOne!.index + 1))
|
||||
return void 0;
|
||||
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())!;
|
||||
|
||||
return this.toPyramids(this.residueOne!, this.residueTwo);
|
||||
}
|
||||
|
||||
private toPyramids(one: DnatcoUtil.Residue, two: DnatcoUtil.Residue) {
|
||||
const indices = DnatcoUtil.getStepIndices(this.data!, this.loc, one);
|
||||
private toPyramids(one: Residue, two: Residue) {
|
||||
const indices = this.getStepIndices(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.PDB_ins_code_1, step.PDB_ins_code_2, step.confal_score, idx));
|
||||
points.push(getPyramid(this.loc, one, two, step.label_alt_id_1, step.label_alt_id_2, step.confal_score, idx));
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -96,8 +121,6 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Michal Malý <michal.maly@ibt.cas.cz>
|
||||
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
export { DnatcoNtCs } from './behavior';
|
||||
export { DnatcoConfalPyramids } from './confal-pyramids/behavior';
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* 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)}
|
||||
`;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
@@ -1,454 +0,0 @@
|
||||
/**
|
||||
* 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)),
|
||||
},
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,6 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const merged = merge(
|
||||
this.controls.behaviors.params,
|
||||
this.plugin.canvas3d!.reprCount
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
@@ -1,223 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* 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>;
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/**
|
||||
* 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?
|
||||
@@ -1,340 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* 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';
|
||||
@@ -15,7 +14,6 @@ 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 };
|
||||
|
||||
@@ -73,28 +71,14 @@ 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);
|
||||
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -118,13 +118,11 @@ export class Mp4Controls extends PluginComponent {
|
||||
}
|
||||
|
||||
private init() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
|
||||
this.sync();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.canvas3d.resized, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));
|
||||
|
||||
@@ -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/';
|
||||
|
||||
@@ -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 2023-01-15T10:00:07-08:00
|
||||
// Generated on 2022-08-20T16:36:05-07:00
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
@@ -343,14 +343,6 @@ 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
|
||||
@@ -558,7 +550,6 @@ 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 = {
|
||||
@@ -670,7 +661,6 @@ 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>>>;
|
||||
@@ -711,7 +701,6 @@ 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;
|
||||
@@ -724,7 +713,6 @@ 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>>>;
|
||||
@@ -798,7 +786,6 @@ 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 = {
|
||||
@@ -932,7 +919,6 @@ 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 = {
|
||||
@@ -1298,9 +1284,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']>;
|
||||
@@ -1331,11 +1317,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.
|
||||
@@ -1496,7 +1482,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
|
||||
@@ -1572,7 +1558,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
|
||||
@@ -1582,7 +1568,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
|
||||
@@ -1628,7 +1614,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
|
||||
@@ -2012,12 +1998,12 @@ export type EmImaging = {
|
||||
/** The magnification indicated by the microscope readout. */
|
||||
readonly nominal_magnification?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
* The specimen temperature maximum (kelvin) for the duration
|
||||
* The specimen temperature maximum (degrees Kelvin) for the duration
|
||||
* of imaging.
|
||||
*/
|
||||
readonly recording_temperature_maximum?: Maybe<Scalars['Float']>;
|
||||
/**
|
||||
* The specimen temperature minimum (kelvin) for the duration
|
||||
* The specimen temperature minimum (degrees Kelvin) for the duration
|
||||
* of imaging.
|
||||
*/
|
||||
readonly recording_temperature_minimum?: Maybe<Scalars['Float']>;
|
||||
@@ -2042,7 +2028,7 @@ export type EmImaging = {
|
||||
/** Foreign key to the EM_SPECIMEN category */
|
||||
readonly specimen_id?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* The mean specimen stage temperature (in kelvin) during imaging
|
||||
* The mean specimen stage temperature (degrees Kelvin) during imaging
|
||||
* in the microscope.
|
||||
*/
|
||||
readonly temperature?: Maybe<Scalars['Float']>;
|
||||
@@ -2229,7 +2215,7 @@ export type EmStaining = {
|
||||
};
|
||||
|
||||
export type EmVitrification = {
|
||||
/** The temperature (in kelvin) of the sample just prior to vitrification. */
|
||||
/** The temperature (in degrees Kelvin) of the sample just prior to vitrification. */
|
||||
readonly chamber_temperature?: Maybe<Scalars['Float']>;
|
||||
/**
|
||||
* This is the name of the cryogen.
|
||||
@@ -2273,7 +2259,7 @@ export type EmVitrification = {
|
||||
/** This data item is a pointer to _em_specimen.id */
|
||||
readonly specimen_id: Scalars['String'];
|
||||
/**
|
||||
* The vitrification temperature (in kelvin), e.g.,
|
||||
* The vitrification temperature (in degrees Kelvin), e.g.,
|
||||
* temperature of the plunge instrument cryogen bath.
|
||||
*/
|
||||
readonly temp?: Maybe<Scalars['Float']>;
|
||||
@@ -2378,14 +2364,6 @@ 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.
|
||||
*
|
||||
@@ -3027,8 +3005,6 @@ 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 = {
|
||||
@@ -3287,29 +3263,6 @@ 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']>;
|
||||
@@ -4246,7 +4199,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']>;
|
||||
@@ -4500,7 +4453,7 @@ export type PdbxPrdAudit = {
|
||||
* An identifier for the wwPDB site creating or modifying the molecule.
|
||||
*
|
||||
* Allowable values:
|
||||
* BMRB, PDBC, PDBE, PDBJ, RCSB
|
||||
* BMRB, PDBC, PDBJ, PDBe, RCSB
|
||||
*
|
||||
*/
|
||||
readonly processing_site?: Maybe<Scalars['String']>;
|
||||
@@ -6864,7 +6817,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. Note that the ENTRY ID part must be upper case. */
|
||||
/** 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. */
|
||||
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>;
|
||||
@@ -6888,9 +6841,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. Note that the ENTRY ID part must be upper case. */
|
||||
/** 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. */
|
||||
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. Note that the ENTRY ID part must be upper case. */
|
||||
/** 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. */
|
||||
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>;
|
||||
@@ -6898,7 +6851,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. Note that the ENTRY ID part must be upper case. */
|
||||
/** 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. */
|
||||
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>;
|
||||
@@ -8468,31 +8421,6 @@ 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
|
||||
@@ -8729,7 +8657,7 @@ export type RcsbEntitySourceOrganism = {
|
||||
* A code indicating the provenance of the source organism details for the entity
|
||||
*
|
||||
* Allowable values:
|
||||
* PDB Primary Data, UniProt
|
||||
* PDB Primary Data
|
||||
*
|
||||
*/
|
||||
readonly provenance_source?: Maybe<Scalars['String']>;
|
||||
@@ -8803,7 +8731,7 @@ export type RcsbEntryContainerIdentifiers = {
|
||||
* Entry identifier for the container.
|
||||
*
|
||||
* Examples:
|
||||
* 4HHB, AF_AFP60325F1, MA_MABAKCEPC0019
|
||||
* 1KIP, 4HHB
|
||||
*
|
||||
*/
|
||||
readonly entry_id: Scalars['String'];
|
||||
@@ -8951,15 +8879,6 @@ 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. */
|
||||
@@ -9036,21 +8955,6 @@ 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 = {
|
||||
@@ -9390,49 +9294,6 @@ 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']>;
|
||||
@@ -10471,7 +10332,7 @@ export type RcsbPolymerEntityContainerIdentifiersReferenceSequenceIdentifiers =
|
||||
* Source of the reference database assignment
|
||||
*
|
||||
* Allowable values:
|
||||
* PDB, RCSB, SIFTS, UniProt
|
||||
* PDB, RCSB, SIFTS
|
||||
*
|
||||
*/
|
||||
readonly provenance_source?: Maybe<Scalars['String']>;
|
||||
@@ -10876,7 +10737,7 @@ export type RcsbPolymerInstanceFeature = {
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* 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
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
@@ -10887,7 +10748,7 @@ export type RcsbPolymerInstanceFeatureAdditionalProperties = {
|
||||
* The additional property name.
|
||||
*
|
||||
* Allowable values:
|
||||
* 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
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
@@ -10957,7 +10818,7 @@ export type RcsbPolymerInstanceFeatureSummary = {
|
||||
* Type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* 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
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
@@ -13516,11 +13377,6 @@ 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.
|
||||
@@ -13671,37 +13527,6 @@ 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.
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* 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 } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}));
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
});
|
||||
@@ -1,264 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* 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];
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -202,7 +202,7 @@ export class ZenodoImportUI extends CollapsableControls<{}, State> {
|
||||
}));
|
||||
} else if (t.name === 'trajectory') {
|
||||
const [topologyUrl, topologyFormat, topologyIsBinary] = t.params.topology.split('|');
|
||||
const [coordinatesUrl, coordinatesFormat] = t.params.coordinates.split('|');
|
||||
const [coordinatesUrl, coordinatesFormat, coordinatesIsBinary] = t.params.coordinates.split('|');
|
||||
|
||||
await this.plugin.runTask(this.plugin.state.data.applyAction(LoadTrajectory, {
|
||||
source: {
|
||||
@@ -216,6 +216,7 @@ export class ZenodoImportUI extends CollapsableControls<{}, State> {
|
||||
coordinates: {
|
||||
url: coordinatesUrl,
|
||||
format: coordinatesFormat as any,
|
||||
isBinary: coordinatesIsBinary === 'true',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Vec4 } from '../../mol-math/linear-algebra';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4';
|
||||
import { Viewport, cameraProject, cameraUnproject } from '../camera/util';
|
||||
|
||||
describe('camera', () => {
|
||||
it('project/unproject', () => {
|
||||
const proj = Mat4.perspective(Mat4(), -1, 1, 1, -1, 1, 100);
|
||||
const invProj = Mat4.invert(Mat4(), proj);
|
||||
|
||||
const c = Vec4();
|
||||
const po = Vec3();
|
||||
|
||||
const vp = Viewport.create(0, 0, 100, 100);
|
||||
const pi = Vec3.create(0, 0, 1);
|
||||
cameraProject(c, pi, vp, proj);
|
||||
expect(Vec4.equals(c, Vec4.create(50, 50, 2.020202, -1))).toBe(true);
|
||||
cameraUnproject(po, c, vp, invProj);
|
||||
expect(Vec3.equals(po, pi)).toBe(true);
|
||||
|
||||
Vec3.set(pi, 0.5, 0.5, 1);
|
||||
cameraProject(c, pi, vp, proj);
|
||||
cameraUnproject(po, c, vp, invProj);
|
||||
expect(Vec3.equals(po, pi)).toBe(true);
|
||||
|
||||
Viewport.set(vp, 50, 50, 100, 100);
|
||||
Vec3.set(pi, 0.5, 0.5, 1);
|
||||
cameraProject(c, pi, vp, proj);
|
||||
cameraUnproject(po, c, vp, invProj);
|
||||
expect(Vec3.equals(po, pi)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -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, snapshot?: Partial<Camera.Snapshot>): Partial<Camera.Snapshot> {
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01);
|
||||
const targetDistance = this.getTargetDistance(r);
|
||||
|
||||
Vec3.sub(this.deltaDirection, snapshot?.target ?? this.target, snapshot?.position ?? this.position);
|
||||
Vec3.sub(this.deltaDirection, this.target, 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,18 +137,6 @@ 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);
|
||||
@@ -172,10 +160,6 @@ 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);
|
||||
@@ -194,7 +178,7 @@ class Camera implements ICamera {
|
||||
getPixelSize(point: Vec3) {
|
||||
// project -> unproject of `point` does not exactly return the same
|
||||
// to get a sufficiently accurate measure we unproject the original
|
||||
// clip position in addition to the one shifted by one pixel
|
||||
// clip position in addition to the one shifted bey one pixel
|
||||
this.project(tmpClip, point);
|
||||
this.unproject(tmpPos1, tmpClip);
|
||||
tmpClip[0] += 1;
|
||||
@@ -276,9 +260,7 @@ namespace Camera {
|
||||
radius: 0,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true,
|
||||
minNear: 5,
|
||||
minFar: 0,
|
||||
clipFar: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -294,8 +276,6 @@ namespace Camera {
|
||||
radiusMax: number
|
||||
fog: number
|
||||
clipFar: boolean
|
||||
minNear: number
|
||||
minFar: number
|
||||
}
|
||||
|
||||
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
|
||||
@@ -312,8 +292,6 @@ namespace Camera {
|
||||
if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
|
||||
if (typeof source.fog !== 'undefined') out.fog = source.fog;
|
||||
if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
|
||||
if (typeof source.minNear !== 'undefined') out.minNear = source.minNear;
|
||||
if (typeof source.minFar !== 'undefined') out.minFar = source.minFar;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -325,8 +303,6 @@ namespace Camera {
|
||||
&& a.radiusMax === b.radiusMax
|
||||
&& a.fog === b.fog
|
||||
&& a.clipFar === b.clipFar
|
||||
&& a.minNear === b.minNear
|
||||
&& a.minFar === b.minFar
|
||||
&& Vec3.exactEquals(a.position, b.position)
|
||||
&& Vec3.exactEquals(a.up, b.up)
|
||||
&& Vec3.exactEquals(a.target, b.target);
|
||||
@@ -394,22 +370,26 @@ function updatePers(camera: Camera) {
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
let { radius, radiusMax, mode, fog, clipFar, minNear, minFar } = camera.state;
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state;
|
||||
if (radius < 0.01) radius = 0.01;
|
||||
|
||||
const normalizedFar = Math.max(clipFar ? radius : radiusMax, minFar);
|
||||
const normalizedFar = clipFar ? radius : radiusMax;
|
||||
const cameraDistance = Vec3.distance(camera.position, camera.target);
|
||||
let near = cameraDistance - radius;
|
||||
let far = cameraDistance + normalizedFar;
|
||||
|
||||
const fogNearFactor = -(50 - fog) / 50;
|
||||
const fogNear = cameraDistance - (normalizedFar * fogNearFactor);
|
||||
const fogFar = far;
|
||||
|
||||
if (mode === 'perspective') {
|
||||
// set at least to 5 to avoid slow sphere impostor rendering
|
||||
near = Math.max(Math.min(radiusMax, minNear), near);
|
||||
far = Math.max(minNear, far);
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
} else {
|
||||
// not too close to 0 as it causes issues with outline rendering
|
||||
near = Math.max(Math.min(radiusMax, minNear), near);
|
||||
far = Math.max(minNear, far);
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
@@ -417,10 +397,6 @@ function updateClip(camera: Camera) {
|
||||
far = near + 0.01;
|
||||
}
|
||||
|
||||
const fogNearFactor = -(50 - fog) / 50;
|
||||
const fogNear = cameraDistance - (normalizedFar * fogNearFactor);
|
||||
const fogFar = far;
|
||||
|
||||
camera.near = near;
|
||||
camera.far = 2 * far; // avoid precision issues distingushing far objects from background
|
||||
camera.fogNear = fogNear;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -13,8 +13,8 @@ import { Camera, ICamera } from '../camera';
|
||||
import { Viewport } from './util';
|
||||
|
||||
export const StereoCameraParams = {
|
||||
eyeSeparation: PD.Numeric(0.062, { min: 0.02, max: 0.1, step: 0.001 }, { description: 'Distance between left and right camera.' }),
|
||||
focus: PD.Numeric(10, { min: 1, max: 20, step: 0.1 }, { description: 'Apparent object distance.' }),
|
||||
eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
|
||||
focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
|
||||
};
|
||||
export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
|
||||
export type StereoCameraProps = PD.Values<typeof StereoCameraParams>
|
||||
|
||||
@@ -77,7 +77,7 @@ export function cameraProject(out: Vec4, point: Vec3, viewport: Viewport, projec
|
||||
|
||||
// transform into window coordinates, set fourth component to 1 / clip.w as in gl_FragCoord.w
|
||||
out[0] = (tmpVec4[0] + 1) * width * 0.5 + x;
|
||||
out[1] = (tmpVec4[1] + 1) * height * 0.5 + y;
|
||||
out[1] = (1 - tmpVec4[1]) * height * 0.5 + y; // flip Y
|
||||
out[2] = (tmpVec4[2] + 1) * 0.5;
|
||||
out[3] = w === 0 ? 0 : 1 / w;
|
||||
return out;
|
||||
@@ -92,7 +92,7 @@ export function cameraUnproject(out: Vec3, point: Vec3 | Vec4, viewport: Viewpor
|
||||
const { x, y, width, height } = viewport;
|
||||
|
||||
const px = point[0] - x;
|
||||
const py = point[1] - y;
|
||||
const py = (height - point[1] - 1) - y;
|
||||
const pz = point[2];
|
||||
|
||||
out[0] = (2 * px) / width - 1;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
@@ -31,7 +30,7 @@ import { PickData } from './passes/pick';
|
||||
import { PickHelper } from './passes/pick';
|
||||
import { ImagePass, ImageProps } from './passes/image';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { addConsoleStatsProvider, isDebugMode, isTimingMode, removeConsoleStatsProvider } from '../mol-util/debug';
|
||||
import { isDebugMode, isTimingMode } from '../mol-util/debug';
|
||||
import { CameraHelperParams } from './helper/camera-helper';
|
||||
import { produce } from 'immer';
|
||||
import { HandleHelperParams } from './helper/handle-helper';
|
||||
@@ -40,10 +39,9 @@ import { Helper } from './helper/helper';
|
||||
import { Passes } from './passes/passes';
|
||||
import { shallowEqual } from '../mol-util';
|
||||
import { MarkingParams } from './passes/marking';
|
||||
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit, GraphicsRenderVariantsDpoit } from '../mol-gl/webgl/render-item';
|
||||
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item';
|
||||
import { degToRad, radToDeg } from '../mol-math/misc';
|
||||
import { AssetManager } from '../mol-util/assets';
|
||||
import { deepClone } from '../mol-util/object';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
camera: PD.Group({
|
||||
@@ -65,7 +63,6 @@ export const Canvas3DParams = {
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
minNear: PD.Numeric(5, { min: 0.1, max: 100, step: 0.1 }, { description: 'Note, may cause performance issues rendering impostors when set too small and cause issues with outline rendering when too close to 0.' }),
|
||||
}, { pivot: 'radius' }),
|
||||
viewport: PD.MappedStatic('canvas', {
|
||||
canvas: PD.Group({}),
|
||||
@@ -86,7 +83,6 @@ export const Canvas3DParams = {
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
@@ -120,8 +116,6 @@ 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,
|
||||
/** true to support multiple Canvas3D objects with a single context */
|
||||
@@ -131,20 +125,14 @@ namespace Canvas3DContext {
|
||||
/** extra pixels to around target to check in case target is empty */
|
||||
pickPadding: 1,
|
||||
enableWboit: true,
|
||||
enableDpoit: false,
|
||||
preferWebGl1: false
|
||||
};
|
||||
export type Attribs = typeof DefaultAttribs
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, assetManager: AssetManager, attribs: Partial<Attribs> = {}): Canvas3DContext {
|
||||
const a = { ...DefaultAttribs, ...attribs };
|
||||
|
||||
if (a.enableWboit && a.enableDpoit) throw new Error('Multiple transparency methods not allowed.');
|
||||
|
||||
const { powerPreference, failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
|
||||
const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
|
||||
const gl = getGLContext(canvas, {
|
||||
powerPreference,
|
||||
failIfMajorPerformanceCaveat,
|
||||
antialias,
|
||||
preserveDrawingBuffer,
|
||||
alpha: true, // the renderer requires an alpha channel
|
||||
@@ -260,7 +248,6 @@ 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>
|
||||
|
||||
@@ -298,7 +285,7 @@ namespace Canvas3D {
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
|
||||
export function create({ webgl, input, passes, attribs, assetManager }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p: Canvas3DProps = { ...deepClone(DefaultCanvas3DParams), ...deepClone(props) };
|
||||
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
|
||||
@@ -307,7 +294,6 @@ 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;
|
||||
|
||||
@@ -316,7 +302,8 @@ namespace Canvas3D {
|
||||
let width = 128;
|
||||
let height = 128;
|
||||
updateViewport();
|
||||
const scene = Scene.create(webgl, passes.draw.dpoitEnabled ? GraphicsRenderVariantsDpoit : (passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended));
|
||||
|
||||
const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended);
|
||||
|
||||
function getSceneRadius() {
|
||||
return scene.boundingSphere.radius * p.sceneRadiusFactor;
|
||||
@@ -327,17 +314,16 @@ namespace Canvas3D {
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far,
|
||||
minNear: p.cameraClipping.minNear,
|
||||
fov: degToRad(p.camera.fov),
|
||||
}, { x, y, width, height }, { pixelScale: attribs.pixelScale });
|
||||
const stereoCamera = new StereoCamera(camera, p.camera.stereo.params);
|
||||
|
||||
const controls = TrackballControls.create(input, camera, scene, p.trackball);
|
||||
const controls = TrackballControls.create(input, camera, p.trackball);
|
||||
const renderer = Renderer.create(webgl, p.renderer);
|
||||
const helper = new Helper(webgl, scene, p);
|
||||
|
||||
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height }, attribs.pickPadding);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, controls, p.interaction);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, p.interaction);
|
||||
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
|
||||
|
||||
passes.draw.postprocessing.background.update(camera, p.postprocessing.background, changed => {
|
||||
@@ -442,7 +428,7 @@ namespace Canvas3D {
|
||||
cam = stereoCamera;
|
||||
}
|
||||
|
||||
if (isTimingMode) webgl.timer.mark('Canvas3D.render', true);
|
||||
if (isTimingMode) webgl.timer.mark('Canvas3D.render');
|
||||
const ctx = { renderer, camera: cam, scene, helper };
|
||||
if (MultiSamplePass.isEnabled(p.multiSample)) {
|
||||
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
|
||||
@@ -595,11 +581,7 @@ namespace Canvas3D {
|
||||
// snapshot the current bounding sphere of visible objects
|
||||
Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
|
||||
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) {
|
||||
commitQueueSize.next(scene.commitQueueSize);
|
||||
return false;
|
||||
}
|
||||
commitQueueSize.next(0);
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
|
||||
|
||||
if (helper.debug.isEnabled) helper.debug.update();
|
||||
if (!p.camera.manualReset && (reprCount.value === 0 || shouldResetCamera())) {
|
||||
@@ -615,32 +597,20 @@ namespace Canvas3D {
|
||||
}
|
||||
|
||||
function consoleStats() {
|
||||
const items = scene.renderables.map(r => ({
|
||||
console.table(scene.renderables.map(r => ({
|
||||
drawCount: r.values.drawCount.ref.value,
|
||||
instanceCount: r.values.instanceCount.ref.value,
|
||||
materialId: r.materialId,
|
||||
renderItemId: r.id,
|
||||
}));
|
||||
|
||||
console.groupCollapsed(`${items.length} RenderItems`);
|
||||
|
||||
if (items.length < 50) {
|
||||
console.table(items);
|
||||
} else {
|
||||
console.log(items);
|
||||
}
|
||||
console.log(JSON.stringify(webgl.stats, undefined, 4));
|
||||
})));
|
||||
console.log(webgl.stats);
|
||||
|
||||
const { texture, attribute, elements } = webgl.resources.getByteCounts();
|
||||
console.log(JSON.stringify({
|
||||
console.log({
|
||||
texture: `${(texture / 1024 / 1024).toFixed(3)} MiB`,
|
||||
attribute: `${(attribute / 1024 / 1024).toFixed(3)} MiB`,
|
||||
elements: `${(elements / 1024 / 1024).toFixed(3)} MiB`,
|
||||
}, undefined, 4));
|
||||
|
||||
console.log(JSON.stringify(webgl.timer.formatedStats(), undefined, 4));
|
||||
|
||||
console.groupEnd();
|
||||
});
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
@@ -709,11 +679,10 @@ namespace Canvas3D {
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius, minNear: camera.state.minNear },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
sceneRadiusFactor: p.sceneRadiusFactor,
|
||||
transparentBackground: p.transparentBackground,
|
||||
dpoitIterations: p.dpoitIterations,
|
||||
viewport: p.viewport,
|
||||
|
||||
postprocessing: { ...p.postprocessing },
|
||||
@@ -746,8 +715,6 @@ namespace Canvas3D {
|
||||
resized.next(+new Date());
|
||||
}
|
||||
|
||||
addConsoleStatsProvider(consoleStats);
|
||||
|
||||
return {
|
||||
webgl,
|
||||
|
||||
@@ -810,7 +777,6 @@ namespace Canvas3D {
|
||||
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
|
||||
didDraw,
|
||||
commited,
|
||||
commitQueueSize,
|
||||
reprCount,
|
||||
resized,
|
||||
setProps: (properties, doNotRequestDraw = false) => {
|
||||
@@ -839,9 +805,6 @@ namespace Canvas3D {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far;
|
||||
}
|
||||
if (props.cameraClipping.minNear !== undefined && props.cameraClipping.minNear !== camera.state.minNear) {
|
||||
cameraState.minNear = props.cameraClipping.minNear;
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (getSceneRadius() / 100) * (100 - props.cameraClipping.radius);
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
@@ -854,13 +817,9 @@ namespace Canvas3D {
|
||||
|
||||
if (props.camera?.helper) helper.camera.setProps(props.camera.helper);
|
||||
if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset;
|
||||
if (props.camera?.stereo !== undefined) {
|
||||
Object.assign(p.camera.stereo, props.camera.stereo);
|
||||
stereoCamera.setProps(p.camera.stereo.params);
|
||||
}
|
||||
if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo);
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
|
||||
if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations;
|
||||
if (props.viewport !== undefined) {
|
||||
const doNotUpdate = p.viewport === props.viewport ||
|
||||
(p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params));
|
||||
@@ -896,7 +855,7 @@ namespace Canvas3D {
|
||||
}
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, passes.draw.dpoitEnabled, props);
|
||||
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
|
||||
},
|
||||
getRenderObjects(): GraphicsRenderObject[] {
|
||||
const renderObjects: GraphicsRenderObject[] = [];
|
||||
@@ -918,7 +877,6 @@ namespace Canvas3D {
|
||||
},
|
||||
dispose: () => {
|
||||
contextRestoredSub.unsubscribe();
|
||||
cancelAnimationFrame(animationFrameHandle);
|
||||
|
||||
markBuffer = [];
|
||||
|
||||
@@ -927,8 +885,6 @@ namespace Canvas3D {
|
||||
controls.dispose();
|
||||
renderer.dispose();
|
||||
interactionHelper.dispose();
|
||||
|
||||
removeConsoleStatsProvider(consoleStats);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -964,4 +920,4 @@ namespace Canvas3D {
|
||||
Viewport.set(controls.viewport, x, y, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -10,25 +10,20 @@
|
||||
|
||||
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput, KeyInput, MoveInput } from '../../mol-util/input/input-observer';
|
||||
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Camera } from '../camera';
|
||||
import { absMax, degToRad } from '../../mol-math/misc';
|
||||
import { Binding } from '../../mol-util/binding';
|
||||
import { Scene } from '../../mol-gl/scene';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
const Key = Binding.TriggerKey;
|
||||
|
||||
export const DefaultTrackballBindings = {
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate', 'Drag using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true, control: true }))], 'Rotate around z-axis (roll)', 'Drag using ${triggers}'),
|
||||
dragPan: Binding([
|
||||
Trigger(B.Flag.Secondary, M.create()),
|
||||
Trigger(B.Flag.Primary, M.create({ control: true }))
|
||||
], 'Pan', 'Drag using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate around z-axis', 'Drag using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan', 'Drag using ${triggers}'),
|
||||
dragZoom: Binding.Empty,
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus', 'Drag using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom', 'Drag using ${triggers}'),
|
||||
@@ -36,22 +31,6 @@ export const DefaultTrackballBindings = {
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom', 'Scroll using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Clip', 'Scroll using ${triggers}'),
|
||||
scrollFocusZoom: Binding.Empty,
|
||||
|
||||
keyMoveForward: Binding([Key('KeyW')], 'Move forward', 'Press ${triggers}'),
|
||||
keyMoveBack: Binding([Key('KeyS')], 'Move back', 'Press ${triggers}'),
|
||||
keyMoveLeft: Binding([Key('KeyA')], 'Move left', 'Press ${triggers}'),
|
||||
keyMoveRight: Binding([Key('KeyD')], 'Move right', 'Press ${triggers}'),
|
||||
keyMoveUp: Binding([Key('KeyR')], 'Move up', 'Press ${triggers}'),
|
||||
keyMoveDown: Binding([Key('KeyF')], 'Move down', 'Press ${triggers}'),
|
||||
keyRollLeft: Binding([Key('KeyQ')], 'Roll left', 'Press ${triggers}'),
|
||||
keyRollRight: Binding([Key('KeyE')], 'Roll right', 'Press ${triggers}'),
|
||||
keyPitchUp: Binding([Key('ArrowUp')], 'Pitch up', 'Press ${triggers}'),
|
||||
keyPitchDown: Binding([Key('ArrowDown')], 'Pitch down', 'Press ${triggers}'),
|
||||
keyYawLeft: Binding([Key('ArrowLeft')], 'Yaw left', 'Press ${triggers}'),
|
||||
keyYawRight: Binding([Key('ArrowRight')], 'Yaw right', 'Press ${triggers}'),
|
||||
|
||||
boostMove: Binding([Key('ShiftLeft')], 'Boost move', 'Press ${triggers}'),
|
||||
enablePointerLock: Binding([Key('Space', M.create({ control: true }))], 'Enable pointer lock', 'Press ${triggers}'),
|
||||
};
|
||||
|
||||
export const TrackballControlsParams = {
|
||||
@@ -60,9 +39,6 @@ export const TrackballControlsParams = {
|
||||
rotateSpeed: PD.Numeric(5.0, { min: 1, max: 10, step: 1 }),
|
||||
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
|
||||
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
|
||||
moveSpeed: PD.Numeric(0.75, { min: 0.1, max: 3, step: 0.1 }),
|
||||
boostMoveFactor: PD.Numeric(5.0, { min: 0.1, max: 10, step: 0.1 }),
|
||||
flyMode: PD.Boolean(false),
|
||||
|
||||
animate: PD.MappedStatic('off', {
|
||||
off: PD.EmptyGroup(),
|
||||
@@ -106,7 +82,6 @@ export { TrackballControls };
|
||||
interface TrackballControls {
|
||||
readonly viewport: Viewport
|
||||
readonly isAnimating: boolean
|
||||
readonly isMoving: boolean
|
||||
|
||||
readonly props: Readonly<TrackballControlsProps>
|
||||
setProps: (props: Partial<TrackballControlsProps>) => void
|
||||
@@ -117,14 +92,8 @@ interface TrackballControls {
|
||||
dispose: () => void
|
||||
}
|
||||
namespace TrackballControls {
|
||||
export function create(input: InputObserver, camera: Camera, scene: Scene, props: Partial<TrackballControlsProps> = {}): TrackballControls {
|
||||
const p: TrackballControlsProps = {
|
||||
...PD.getDefaultValues(TrackballControlsParams),
|
||||
...props,
|
||||
// include default bindings for backwards state compatibility
|
||||
bindings: { ...DefaultTrackballBindings, ...props.bindings }
|
||||
};
|
||||
const b = p.bindings;
|
||||
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
|
||||
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props };
|
||||
|
||||
const viewport = Viewport.clone(camera.viewport);
|
||||
|
||||
@@ -135,11 +104,6 @@ namespace TrackballControls {
|
||||
const wheelSub = input.wheel.subscribe(onWheel);
|
||||
const pinchSub = input.pinch.subscribe(onPinch);
|
||||
const gestureSub = input.gesture.subscribe(onGesture);
|
||||
const keyDownSub = input.keyDown.subscribe(onKeyDown);
|
||||
const keyUpSub = input.keyUp.subscribe(onKeyUp);
|
||||
const moveSub = input.move.subscribe(onMove);
|
||||
const lockSub = input.lock.subscribe(onLock);
|
||||
const leaveSub = input.leave.subscribe(onLeave);
|
||||
|
||||
let _isInteracting = false;
|
||||
|
||||
@@ -153,12 +117,9 @@ namespace TrackballControls {
|
||||
const _rotLastAxis = Vec3();
|
||||
let _rotLastAngle = 0;
|
||||
|
||||
const _rollPrev = Vec2();
|
||||
const _rollCurr = Vec2();
|
||||
let _rollLastAngle = 0;
|
||||
|
||||
let _pitchLastAngle = 0;
|
||||
let _yawLastAngle = 0;
|
||||
const _zRotPrev = Vec2();
|
||||
const _zRotCurr = Vec2();
|
||||
let _zRotLastAngle = 0;
|
||||
|
||||
const _zoomStart = Vec2();
|
||||
const _zoomEnd = Vec2();
|
||||
@@ -188,7 +149,7 @@ namespace TrackballControls {
|
||||
return Vec2.set(
|
||||
mouseOnCircleVec2,
|
||||
(pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5),
|
||||
(viewport.height + 2 * (viewport.y - pageY)) / viewport.width // viewport.width intentional
|
||||
(viewport.height + 2 * (viewport.y - pageY)) / viewport.width // screen.width intentional
|
||||
);
|
||||
}
|
||||
|
||||
@@ -242,74 +203,26 @@ namespace TrackballControls {
|
||||
Vec2.copy(_rotPrev, _rotCurr);
|
||||
}
|
||||
|
||||
const rollQuat = Quat();
|
||||
const rollDir = Vec3();
|
||||
const zRotQuat = Quat();
|
||||
|
||||
function rollCamera() {
|
||||
const k = (keyState.rollRight - keyState.rollLeft) / 45;
|
||||
const dx = (_rollCurr[0] - _rollPrev[0]) * -Math.sign(_rollCurr[1]);
|
||||
const dy = (_rollCurr[1] - _rollPrev[1]) * -Math.sign(_rollCurr[0]);
|
||||
const angle = -p.rotateSpeed * (-dx + dy) + k;
|
||||
function zRotateCamera() {
|
||||
const dx = _zRotCurr[0] - _zRotPrev[0];
|
||||
const dy = _zRotCurr[1] - _zRotPrev[1];
|
||||
const angle = p.rotateSpeed * (-dx + dy) * -0.05;
|
||||
|
||||
if (angle) {
|
||||
Vec3.normalize(rollDir, _eye);
|
||||
Quat.setAxisAngle(rollQuat, rollDir, angle);
|
||||
Vec3.transformQuat(camera.up, camera.up, rollQuat);
|
||||
_rollLastAngle = angle;
|
||||
} else if (!p.staticMoving && _rollLastAngle) {
|
||||
_rollLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.normalize(rollDir, _eye);
|
||||
Quat.setAxisAngle(rollQuat, rollDir, _rollLastAngle);
|
||||
Vec3.transformQuat(camera.up, camera.up, rollQuat);
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(zRotQuat, _eye, angle);
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat);
|
||||
_zRotLastAngle = angle;
|
||||
} else if (!p.staticMoving && _zRotLastAngle) {
|
||||
_zRotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle);
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat);
|
||||
}
|
||||
|
||||
Vec2.copy(_rollPrev, _rollCurr);
|
||||
}
|
||||
|
||||
const pitchQuat = Quat();
|
||||
const pitchDir = Vec3();
|
||||
|
||||
function pitchCamera() {
|
||||
const m = (keyState.pitchUp - keyState.pitchDown) / (p.flyMode ? 360 : 90);
|
||||
const angle = -p.rotateSpeed * m;
|
||||
|
||||
if (angle) {
|
||||
Vec3.cross(pitchDir, _eye, camera.up);
|
||||
Vec3.normalize(pitchDir, pitchDir);
|
||||
Quat.setAxisAngle(pitchQuat, pitchDir, angle);
|
||||
Vec3.transformQuat(_eye, _eye, pitchQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, pitchQuat);
|
||||
_pitchLastAngle = angle;
|
||||
} else if (!p.staticMoving && _pitchLastAngle) {
|
||||
_pitchLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.cross(pitchDir, _eye, camera.up);
|
||||
Vec3.normalize(pitchDir, pitchDir);
|
||||
Quat.setAxisAngle(pitchQuat, pitchDir, _pitchLastAngle);
|
||||
Vec3.transformQuat(_eye, _eye, pitchQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, pitchQuat);
|
||||
}
|
||||
}
|
||||
|
||||
const yawQuat = Quat();
|
||||
const yawDir = Vec3();
|
||||
|
||||
function yawCamera() {
|
||||
const m = (keyState.yawRight - keyState.yawLeft) / (p.flyMode ? 360 : 90);
|
||||
const angle = -p.rotateSpeed * m;
|
||||
|
||||
if (angle) {
|
||||
Vec3.normalize(yawDir, camera.up);
|
||||
Quat.setAxisAngle(yawQuat, yawDir, angle);
|
||||
Vec3.transformQuat(_eye, _eye, yawQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, yawQuat);
|
||||
_yawLastAngle = angle;
|
||||
} else if (!p.staticMoving && _yawLastAngle) {
|
||||
_yawLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.normalize(yawDir, camera.up);
|
||||
Quat.setAxisAngle(yawQuat, yawDir, _yawLastAngle);
|
||||
Vec3.transformQuat(_eye, _eye, yawQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, yawQuat);
|
||||
}
|
||||
Vec2.copy(_zRotPrev, _zRotCurr);
|
||||
}
|
||||
|
||||
function zoomCamera() {
|
||||
@@ -370,92 +283,6 @@ namespace TrackballControls {
|
||||
}
|
||||
}
|
||||
|
||||
const keyState = {
|
||||
moveUp: 0, moveDown: 0, moveLeft: 0, moveRight: 0, moveForward: 0, moveBack: 0,
|
||||
pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0,
|
||||
boostMove: 0,
|
||||
};
|
||||
|
||||
const moveDir = Vec3();
|
||||
const moveEye = Vec3();
|
||||
|
||||
function moveCamera(deltaT: number) {
|
||||
Vec3.sub(moveEye, camera.position, camera.target);
|
||||
const minDistance = Math.max(camera.state.minNear, p.minDistance);
|
||||
Vec3.setMagnitude(moveEye, moveEye, minDistance);
|
||||
|
||||
const moveSpeed = deltaT * (60 / 1000) * p.moveSpeed * (keyState.boostMove === 1 ? p.boostMoveFactor : 1);
|
||||
|
||||
if (keyState.moveForward === 1) {
|
||||
Vec3.normalize(moveDir, moveEye);
|
||||
Vec3.scaleAndSub(camera.position, camera.position, moveDir, moveSpeed);
|
||||
const dt = Vec3.distance(camera.target, camera.position);
|
||||
const ds = Vec3.distance(scene.boundingSphereVisible.center, camera.position);
|
||||
if (p.flyMode || input.pointerLock || (dt < minDistance && ds < camera.state.radiusMax)) {
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
}
|
||||
}
|
||||
|
||||
if (keyState.moveBack === 1) {
|
||||
Vec3.normalize(moveDir, moveEye);
|
||||
Vec3.scaleAndAdd(camera.position, camera.position, moveDir, moveSpeed);
|
||||
if (p.flyMode || input.pointerLock) {
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
}
|
||||
}
|
||||
|
||||
if (keyState.moveLeft === 1) {
|
||||
Vec3.cross(moveDir, moveEye, camera.up);
|
||||
Vec3.normalize(moveDir, moveDir);
|
||||
if (p.flyMode || input.pointerLock) {
|
||||
Vec3.scaleAndAdd(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
} else {
|
||||
Vec3.scaleAndSub(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, _eye);
|
||||
}
|
||||
}
|
||||
|
||||
if (keyState.moveRight === 1) {
|
||||
Vec3.cross(moveDir, moveEye, camera.up);
|
||||
Vec3.normalize(moveDir, moveDir);
|
||||
if (p.flyMode || input.pointerLock) {
|
||||
Vec3.scaleAndSub(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
} else {
|
||||
Vec3.scaleAndAdd(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, _eye);
|
||||
}
|
||||
}
|
||||
|
||||
if (keyState.moveUp === 1) {
|
||||
Vec3.normalize(moveDir, camera.up);
|
||||
if (p.flyMode || input.pointerLock) {
|
||||
Vec3.scaleAndAdd(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
} else {
|
||||
Vec3.scaleAndSub(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, _eye);
|
||||
}
|
||||
}
|
||||
|
||||
if (keyState.moveDown === 1) {
|
||||
Vec3.normalize(moveDir, camera.up);
|
||||
if (p.flyMode || input.pointerLock) {
|
||||
Vec3.scaleAndSub(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
} else {
|
||||
Vec3.scaleAndAdd(camera.position, camera.position, moveDir, moveSpeed);
|
||||
Vec3.sub(camera.target, camera.position, _eye);
|
||||
}
|
||||
}
|
||||
|
||||
if (p.flyMode || input.pointerLock) {
|
||||
const cameraDistance = Vec3.distance(camera.position, scene.boundingSphereVisible.center);
|
||||
camera.setState({ minFar: cameraDistance + scene.boundingSphereVisible.radius });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the distance between object and target is within the min/max distance
|
||||
* and not too large compared to `camera.state.radiusMax`
|
||||
@@ -492,19 +319,15 @@ namespace TrackballControls {
|
||||
/** Update the object's position, direction and up vectors */
|
||||
function update(t: number) {
|
||||
if (lastUpdated === t) return;
|
||||
|
||||
const deltaT = t - lastUpdated;
|
||||
if (lastUpdated > 0) {
|
||||
if (p.animate.name === 'spin') spin(deltaT);
|
||||
else if (p.animate.name === 'rock') rock(deltaT);
|
||||
if (p.animate.name === 'spin') spin(t - lastUpdated);
|
||||
else if (p.animate.name === 'rock') rock(t - lastUpdated);
|
||||
}
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
rotateCamera();
|
||||
rollCamera();
|
||||
pitchCamera();
|
||||
yawCamera();
|
||||
zRotateCamera();
|
||||
zoomCamera();
|
||||
focusCamera();
|
||||
panCamera();
|
||||
@@ -512,15 +335,6 @@ namespace TrackballControls {
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
checkDistances();
|
||||
|
||||
if (lastUpdated > 0) {
|
||||
// clamp the maximum step size at 15 frames to avoid too big jumps
|
||||
// TODO: make this a parameter?
|
||||
moveCamera(Math.min(deltaT, 15 * 1000 / 60));
|
||||
}
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
checkDistances();
|
||||
|
||||
if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) {
|
||||
Vec3.copy(lastPosition, camera.position);
|
||||
}
|
||||
@@ -549,28 +363,24 @@ namespace TrackballControls {
|
||||
_isInteracting = true;
|
||||
resetRock(); // start rocking from the center after interactions
|
||||
|
||||
const dragRotate = Binding.match(b.dragRotate, buttons, modifiers);
|
||||
const dragRotateZ = Binding.match(b.dragRotateZ, buttons, modifiers);
|
||||
const dragPan = Binding.match(b.dragPan, buttons, modifiers);
|
||||
const dragZoom = Binding.match(b.dragZoom, buttons, modifiers);
|
||||
const dragFocus = Binding.match(b.dragFocus, buttons, modifiers);
|
||||
const dragFocusZoom = Binding.match(b.dragFocusZoom, buttons, modifiers);
|
||||
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
|
||||
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
|
||||
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers);
|
||||
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers);
|
||||
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers);
|
||||
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers);
|
||||
|
||||
getMouseOnCircle(pageX, pageY);
|
||||
getMouseOnScreen(pageX, pageY);
|
||||
|
||||
const pr = input.pixelRatio;
|
||||
const vx = (x * pr - viewport.width / 2 - viewport.x) / viewport.width;
|
||||
const vy = -(input.height - y * pr - viewport.height / 2 - viewport.y) / viewport.height;
|
||||
|
||||
if (isStart) {
|
||||
if (dragRotate) {
|
||||
Vec2.copy(_rotCurr, mouseOnCircleVec2);
|
||||
Vec2.copy(_rotPrev, _rotCurr);
|
||||
}
|
||||
if (dragRotateZ) {
|
||||
Vec2.set(_rollCurr, vx, vy);
|
||||
Vec2.copy(_rollPrev, _rollCurr);
|
||||
Vec2.copy(_zRotCurr, mouseOnCircleVec2);
|
||||
Vec2.copy(_zRotPrev, _zRotCurr);
|
||||
}
|
||||
if (dragZoom || dragFocusZoom) {
|
||||
Vec2.copy(_zoomStart, mouseOnScreenVec2);
|
||||
@@ -587,7 +397,7 @@ namespace TrackballControls {
|
||||
}
|
||||
|
||||
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2);
|
||||
if (dragRotateZ) Vec2.set(_rollCurr, vx, vy);
|
||||
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2);
|
||||
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2);
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2);
|
||||
if (dragFocusZoom) {
|
||||
@@ -608,16 +418,16 @@ namespace TrackballControls {
|
||||
if (delta < -p.maxWheelDelta) delta = -p.maxWheelDelta;
|
||||
else if (delta > p.maxWheelDelta) delta = p.maxWheelDelta;
|
||||
|
||||
if (Binding.match(b.scrollZoom, buttons, modifiers)) {
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_zoomEnd[1] += delta;
|
||||
}
|
||||
if (Binding.match(b.scrollFocus, buttons, modifiers)) {
|
||||
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
|
||||
_focusEnd[1] += delta;
|
||||
}
|
||||
}
|
||||
|
||||
function onPinch({ fractionDelta, buttons, modifiers }: PinchInput) {
|
||||
if (Binding.match(b.scrollZoom, buttons, modifiers)) {
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_isInteracting = true;
|
||||
_zoomEnd[1] += p.gestureScaleFactor * fractionDelta;
|
||||
}
|
||||
@@ -628,136 +438,6 @@ namespace TrackballControls {
|
||||
_zoomEnd[1] += p.gestureScaleFactor * deltaScale;
|
||||
}
|
||||
|
||||
function onMove({ movementX, movementY }: MoveInput) {
|
||||
if (!input.pointerLock || movementX === undefined || movementY === undefined) return;
|
||||
|
||||
const cx = viewport.width * 0.5 - viewport.x;
|
||||
const cy = viewport.height * 0.5 - viewport.y;
|
||||
|
||||
Vec2.copy(_rotPrev, getMouseOnCircle(cx, cy));
|
||||
Vec2.copy(_rotCurr, getMouseOnCircle(movementX + cx, movementY + cy));
|
||||
}
|
||||
|
||||
function onKeyDown({ modifiers, code, x, y }: KeyInput) {
|
||||
if (outsideViewport(x, y)) return;
|
||||
|
||||
if (Binding.matchKey(b.keyMoveForward, code, modifiers)) {
|
||||
keyState.moveForward = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers)) {
|
||||
keyState.moveBack = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers)) {
|
||||
keyState.moveLeft = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers)) {
|
||||
keyState.moveRight = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers)) {
|
||||
keyState.moveUp = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers)) {
|
||||
keyState.moveDown = 1;
|
||||
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers)) {
|
||||
keyState.rollLeft = 1;
|
||||
} else if (Binding.matchKey(b.keyRollRight, code, modifiers)) {
|
||||
keyState.rollRight = 1;
|
||||
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers)) {
|
||||
keyState.pitchUp = 1;
|
||||
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers)) {
|
||||
keyState.pitchDown = 1;
|
||||
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers)) {
|
||||
keyState.yawLeft = 1;
|
||||
} else if (Binding.matchKey(b.keyYawRight, code, modifiers)) {
|
||||
keyState.yawRight = 1;
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.boostMove, code, modifiers)) {
|
||||
keyState.boostMove = 1;
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.enablePointerLock, code, modifiers)) {
|
||||
input.requestPointerLock(viewport);
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyUp({ modifiers, code, x, y }: KeyInput) {
|
||||
if (outsideViewport(x, y)) return;
|
||||
|
||||
if (Binding.matchKey(b.keyMoveForward, code, modifiers)) {
|
||||
keyState.moveForward = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers)) {
|
||||
keyState.moveBack = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers)) {
|
||||
keyState.moveLeft = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers)) {
|
||||
keyState.moveRight = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers)) {
|
||||
keyState.moveUp = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers)) {
|
||||
keyState.moveDown = 0;
|
||||
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers)) {
|
||||
keyState.rollLeft = 0;
|
||||
} else if (Binding.matchKey(b.keyRollRight, code, modifiers)) {
|
||||
keyState.rollRight = 0;
|
||||
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers)) {
|
||||
keyState.pitchUp = 0;
|
||||
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers)) {
|
||||
keyState.pitchDown = 0;
|
||||
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers)) {
|
||||
keyState.yawLeft = 0;
|
||||
} else if (Binding.matchKey(b.keyYawRight, code, modifiers)) {
|
||||
keyState.yawRight = 0;
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.boostMove, code, modifiers)) {
|
||||
keyState.boostMove = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function initCameraMove() {
|
||||
Vec3.sub(moveEye, camera.position, camera.target);
|
||||
const minDistance = Math.max(camera.state.minNear, p.minDistance);
|
||||
Vec3.setMagnitude(moveEye, moveEye, minDistance);
|
||||
Vec3.sub(camera.target, camera.position, moveEye);
|
||||
|
||||
const cameraDistance = Vec3.distance(camera.position, scene.boundingSphereVisible.center);
|
||||
camera.setState({ minFar: cameraDistance + scene.boundingSphereVisible.radius });
|
||||
}
|
||||
|
||||
function resetCameraMove() {
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
const cameraDistance = Vec3.distance(camera.position, center);
|
||||
if (cameraDistance > radius) {
|
||||
const focus = camera.getFocus(center, radius);
|
||||
camera.setState({ ...focus, minFar: 0 });
|
||||
} else {
|
||||
camera.setState({
|
||||
minFar: 0,
|
||||
radius: scene.boundingSphereVisible.radius,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onLock(isLocked: boolean) {
|
||||
if (isLocked) {
|
||||
initCameraMove();
|
||||
} else {
|
||||
resetCameraMove();
|
||||
}
|
||||
}
|
||||
|
||||
function onLeave() {
|
||||
keyState.moveForward = 0;
|
||||
keyState.moveBack = 0;
|
||||
keyState.moveLeft = 0;
|
||||
keyState.moveRight = 0;
|
||||
keyState.moveUp = 0;
|
||||
keyState.moveDown = 0;
|
||||
keyState.rollLeft = 0;
|
||||
keyState.rollRight = 0;
|
||||
keyState.pitchUp = 0;
|
||||
keyState.pitchDown = 0;
|
||||
keyState.yawLeft = 0;
|
||||
keyState.yawRight = 0;
|
||||
keyState.boostMove = 0;
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
@@ -767,11 +447,6 @@ namespace TrackballControls {
|
||||
pinchSub.unsubscribe();
|
||||
gestureSub.unsubscribe();
|
||||
interactionEndSub.unsubscribe();
|
||||
keyDownSub.unsubscribe();
|
||||
keyUpSub.unsubscribe();
|
||||
moveSub.unsubscribe();
|
||||
lockSub.unsubscribe();
|
||||
leaveSub.unsubscribe();
|
||||
}
|
||||
|
||||
const _spinSpeed = Vec2.create(0.005, 0);
|
||||
@@ -814,31 +489,13 @@ namespace TrackballControls {
|
||||
return {
|
||||
viewport,
|
||||
get isAnimating() { return p.animate.name !== 'off'; },
|
||||
get isMoving() {
|
||||
return (
|
||||
keyState.moveForward === 1 || keyState.moveBack === 1 ||
|
||||
keyState.moveLeft === 1 || keyState.moveRight === 1 ||
|
||||
keyState.moveUp === 1 || keyState.moveDown === 1 ||
|
||||
keyState.rollLeft === 1 || keyState.rollRight === 1 ||
|
||||
keyState.pitchUp === 1 || keyState.pitchDown === 1 ||
|
||||
keyState.yawLeft === 1 || keyState.yawRight === 1
|
||||
);
|
||||
},
|
||||
|
||||
get props() { return p as Readonly<TrackballControlsProps>; },
|
||||
setProps: (props: Partial<TrackballControlsProps>) => {
|
||||
if (props.animate?.name === 'rock' && p.animate.name !== 'rock') {
|
||||
resetRock(); // start rocking from the center
|
||||
}
|
||||
if (props.flyMode !== undefined && props.flyMode !== p.flyMode) {
|
||||
if (props.flyMode) {
|
||||
initCameraMove();
|
||||
} else {
|
||||
resetCameraMove();
|
||||
}
|
||||
}
|
||||
Object.assign(p, props);
|
||||
Object.assign(b, props.bindings);
|
||||
},
|
||||
|
||||
start,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,8 +11,6 @@ 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';
|
||||
@@ -25,36 +23,19 @@ 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 = {
|
||||
alpha: PD.Numeric(0.51, { min: 0, max: 1, step: 0.01 }, { isEssential: true, label: 'Opacity' }),
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
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>
|
||||
@@ -75,9 +56,7 @@ export class CameraHelper {
|
||||
axes: { name: 'off', params: {} }
|
||||
};
|
||||
|
||||
private meshRenderObject: GraphicsRenderObject | undefined;
|
||||
private textRenderObject: GraphicsRenderObject | undefined;
|
||||
private pixelRatio = 1;
|
||||
private renderObject: GraphicsRenderObject | undefined;
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl, GraphicsRenderVariantsBlended);
|
||||
@@ -95,20 +74,9 @@ export class CameraHelper {
|
||||
p.axes.name = props.axes.name;
|
||||
if (props.axes.name === 'on') {
|
||||
this.scene.clear();
|
||||
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;
|
||||
}
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
|
||||
this.renderObject = createAxesRenderObject(params);
|
||||
this.scene.add(this.renderObject);
|
||||
this.scene.commit();
|
||||
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200);
|
||||
@@ -126,29 +94,19 @@ export class CameraHelper {
|
||||
|
||||
getLoci(pickingId: PickingId) {
|
||||
const { objectId, groupId, instanceId } = pickingId;
|
||||
if ((
|
||||
(!this.meshRenderObject || objectId !== this.meshRenderObject.id) &&
|
||||
(!this.textRenderObject || objectId !== this.textRenderObject.id)
|
||||
) || groupId === CameraHelperAxis.None) return EmptyLoci;
|
||||
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
|
||||
return CameraAxesLoci(this, groupId, instanceId);
|
||||
}
|
||||
|
||||
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
|
||||
if (!this.renderObject) return false;
|
||||
if (!isCameraAxesLoci(loci)) return false;
|
||||
let changed = false;
|
||||
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;
|
||||
}
|
||||
const groupCount = this.renderObject.values.uGroupCount.ref.value;
|
||||
const { elements } = loci;
|
||||
for (const { groupId, instanceId } of elements) {
|
||||
const idx = instanceId * groupCount + groupId;
|
||||
if (apply(Interval.ofSingleton(idx))) changed = true;
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
@@ -159,82 +117,42 @@ export class CameraHelper {
|
||||
if (!isCameraAxesLoci(loci)) return false;
|
||||
if (loci.data !== this) return false;
|
||||
}
|
||||
return (
|
||||
Visual.mark(this.meshRenderObject, loci, action, this.eachGroup) ||
|
||||
Visual.mark(this.textRenderObject, loci, action, this.eachGroup)
|
||||
);
|
||||
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
|
||||
}
|
||||
|
||||
update(camera: ICamera) {
|
||||
if (!this.meshRenderObject || this.props.axes.name === 'off') return;
|
||||
|
||||
if (this.pixelRatio !== this.webgl.pixelRatio) {
|
||||
this.setProps(this.props);
|
||||
}
|
||||
if (!this.renderObject) return;
|
||||
|
||||
updateCamera(this.camera, camera.viewport, camera.viewOffset);
|
||||
Mat4.extractRotation(this.scene.view, camera.view);
|
||||
|
||||
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);
|
||||
}
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
export enum CameraHelperAxis {
|
||||
export const enum CameraHelperAxis {
|
||||
None = 0,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
XY,
|
||||
XZ,
|
||||
YZ,
|
||||
Origin
|
||||
YZ
|
||||
}
|
||||
|
||||
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';
|
||||
function getAxisLabel(axis: number) {
|
||||
switch (axis) {
|
||||
case CameraHelperAxis.X: return `${x} Axis`;
|
||||
case CameraHelperAxis.Y: return `${y} Axis`;
|
||||
case CameraHelperAxis.Z: return `${z} Axis`;
|
||||
case CameraHelperAxis.XY: return `${x}${y} Plane`;
|
||||
case CameraHelperAxis.XZ: return `${x}${z} Plane`;
|
||||
case CameraHelperAxis.YZ: return `${y}${z} Plane`;
|
||||
case CameraHelperAxis.Origin: return 'Origin';
|
||||
case CameraHelperAxis.X: return 'X Axis';
|
||||
case CameraHelperAxis.Y: return 'Y Axis';
|
||||
case CameraHelperAxis.Z: return 'Z Axis';
|
||||
case CameraHelperAxis.XY: return 'XY Plane';
|
||||
case CameraHelperAxis.XZ: return 'XZ Plane';
|
||||
case CameraHelperAxis.YZ: return 'YZ Plane';
|
||||
default: return 'Axes';
|
||||
}
|
||||
}
|
||||
@@ -242,7 +160,7 @@ function getAxisLabel(axis: number, cameraHelper: CameraHelper) {
|
||||
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
|
||||
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
|
||||
void 0 /** bounding sphere */,
|
||||
() => getAxisLabel(groupId, cameraHelper));
|
||||
() => getAxisLabel(groupId));
|
||||
}
|
||||
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
|
||||
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
|
||||
@@ -279,17 +197,15 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
|
||||
}
|
||||
|
||||
function createAxesMesh(props: AxesProps, mesh?: Mesh) {
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh);
|
||||
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 radius = 0.075 * scale;
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
|
||||
|
||||
state.currentGroup = CameraHelperAxis.Origin;
|
||||
state.currentGroup = CameraHelperAxis.None;
|
||||
addSphere(state, Vec3.origin, radius, 2);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.X;
|
||||
@@ -304,102 +220,50 @@ function createAxesMesh(props: AxesProps, mesh?: Mesh) {
|
||||
addSphere(state, z, radius, 2);
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
|
||||
|
||||
if (props.showPlanes) {
|
||||
Vec3.scale(x, x, 0.5);
|
||||
Vec3.scale(y, y, 0.5);
|
||||
Vec3.scale(z, z, 0.5);
|
||||
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 getAxesMeshShape(props: AxesProps, shape?: Shape<Mesh>) {
|
||||
function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
|
||||
const scale = 100 * props.scale;
|
||||
const mesh = createAxesMesh(props, shape?.geometry);
|
||||
const mesh = createAxesMesh(scale, shape?.geometry);
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
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;
|
||||
case 1: return props.colorX;
|
||||
case 2: return props.colorY;
|
||||
case 3: return props.colorZ;
|
||||
default: return ColorNames.grey;
|
||||
}
|
||||
};
|
||||
return Shape.create('axes-mesh', {}, mesh, getColor, () => 1, () => '');
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '');
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
function createAxesRenderObject(props: AxesProps) {
|
||||
const shape = getAxesShape(props);
|
||||
return Shape.createRenderObject(shape, props);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -54,7 +54,6 @@ export class HandleHelper {
|
||||
};
|
||||
|
||||
private renderObject: GraphicsRenderObject | undefined;
|
||||
private pixelRatio = 1;
|
||||
|
||||
private _transform = Mat4();
|
||||
getBoundingSphere(out: Sphere3D, instanceId: number) {
|
||||
@@ -72,11 +71,7 @@ export class HandleHelper {
|
||||
p.handle.name = props.handle.name;
|
||||
if (props.handle.name === 'on') {
|
||||
this.scene.clear();
|
||||
this.pixelRatio = this.webgl.pixelRatio;
|
||||
const params = {
|
||||
...props.handle.params,
|
||||
scale: props.handle.params.scale * 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();
|
||||
@@ -96,10 +91,6 @@ 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);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -13,14 +13,13 @@ import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Camera } from '../camera';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Bond } from '../../mol-model/structure';
|
||||
import { TrackballControls } from '../controls/trackball';
|
||||
|
||||
type Canvas3D = import('../canvas3d').Canvas3D
|
||||
type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
|
||||
type DragEvent = import('../canvas3d').Canvas3D.DragEvent
|
||||
type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
|
||||
|
||||
enum InputEvent { Move, Click, Drag }
|
||||
const enum InputEvent { Move, Click, Drag }
|
||||
|
||||
const tmpPosA = Vec3();
|
||||
const tmpPos = Vec3();
|
||||
@@ -69,7 +68,7 @@ export class Canvas3dInteractionHelper {
|
||||
}
|
||||
|
||||
private identify(e: InputEvent, t: number) {
|
||||
const xyChanged = this.startX !== this.endX || this.startY !== this.endY || (this.input.pointerLock && !this.controls.isMoving);
|
||||
const xyChanged = this.startX !== this.endX || this.startY !== this.endY;
|
||||
|
||||
if (e === InputEvent.Drag) {
|
||||
if (xyChanged && !this.outsideViewport(this.startX, this.startY)) {
|
||||
@@ -189,7 +188,7 @@ export class Canvas3dInteractionHelper {
|
||||
this.ev.dispose();
|
||||
}
|
||||
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private lociGetter: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, private controls: TrackballControls, props: Partial<Canvas3dInteractionHelperProps> = {}) {
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private lociGetter: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, props: Partial<Canvas3dInteractionHelperProps> = {}) {
|
||||
this.props = { ...PD.getDefaultValues(Canvas3dInteractionHelperParams), ...props };
|
||||
|
||||
input.drag.subscribe(({ x, y, buttons, button, modifiers }) => {
|
||||
@@ -198,12 +197,8 @@ export class Canvas3dInteractionHelper {
|
||||
this.drag(x, y, buttons, button, modifiers);
|
||||
});
|
||||
|
||||
input.move.subscribe(({ x, y, inside, buttons, button, modifiers, onElement }) => {
|
||||
input.move.subscribe(({ x, y, inside, buttons, button, modifiers }) => {
|
||||
if (!inside || this.isInteracting) return;
|
||||
if (!onElement) {
|
||||
this.leave();
|
||||
return;
|
||||
}
|
||||
// console.log('move');
|
||||
this.move(x, y, buttons, button, modifiers);
|
||||
});
|
||||
|
||||
@@ -49,7 +49,6 @@ const SkyboxParams = {
|
||||
pz: PD.File({ label: 'Positive Z / Front', accept: 'image/*' }),
|
||||
}, { isExpanded: true, label: 'Files' }),
|
||||
}),
|
||||
blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or when "EXT_shader_texture_lod" is available.' }),
|
||||
...SharedParams,
|
||||
};
|
||||
type SkyboxProps = PD.Values<typeof SkyboxParams>
|
||||
@@ -171,7 +170,6 @@ export class BackgroundPass {
|
||||
Mat4.invert(m, m);
|
||||
ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m);
|
||||
|
||||
ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness);
|
||||
@@ -369,7 +367,7 @@ function getSkyboxTexture(ctx: WebGLContext, assetManager: AssetManager, faces:
|
||||
const cubeAssets = getCubeAssets(assetManager, faces);
|
||||
const cubeFaces = getCubeFaces(assetManager, cubeAssets);
|
||||
const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz];
|
||||
const texture = ctx.resources.cubeTexture(cubeFaces, true, onload);
|
||||
const texture = ctx.resources.cubeTexture(cubeFaces, false, onload);
|
||||
return { texture, assets };
|
||||
}
|
||||
|
||||
@@ -426,15 +424,12 @@ const BackgroundSchema = {
|
||||
uGradientColorA: UniformSpec('v3'),
|
||||
uGradientColorB: UniformSpec('v3'),
|
||||
uGradientRatio: UniformSpec('f'),
|
||||
uBlur: UniformSpec('f'),
|
||||
uOpacity: UniformSpec('f'),
|
||||
uSaturation: UniformSpec('f'),
|
||||
uLightness: UniformSpec('f'),
|
||||
dVariant: DefineSpec('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']),
|
||||
};
|
||||
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag, {
|
||||
shaderTextureLod: 'optional'
|
||||
});
|
||||
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag);
|
||||
type BackgroundRenderable = ComputeRenderable<Values<typeof BackgroundSchema>>
|
||||
|
||||
function getBackgroundRenderable(ctx: WebGLContext, width: number, height: number): BackgroundRenderable {
|
||||
@@ -453,7 +448,6 @@ function getBackgroundRenderable(ctx: WebGLContext, width: number, height: numbe
|
||||
uGradientColorA: ValueCell.create(Vec3()),
|
||||
uGradientColorB: ValueCell.create(Vec3()),
|
||||
uGradientRatio: ValueCell.create(0.5),
|
||||
uBlur: ValueCell.create(0),
|
||||
uOpacity: ValueCell.create(1),
|
||||
uSaturation: ValueCell.create(0),
|
||||
uLightness: ValueCell.create(0),
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* Adapted from https://github.com/tsherif/webgl2examples, The MIT License, Copyright © 2017 Tarek Sherif, Shuai Shao
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { quad_vert } from '../../mol-gl/shader/quad.vert';
|
||||
import { evaluateDpoit_frag } from '../../mol-gl/shader/evaluate-dpoit.frag';
|
||||
import { blendBackDpoit_frag } from '../../mol-gl/shader/blend-back-dpoit.frag';
|
||||
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../mol-gl/webgl/compat';
|
||||
|
||||
const BlendBackDpoitSchema = {
|
||||
...QuadSchema,
|
||||
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
};
|
||||
const BlendBackDpoitShaderCode = ShaderCode('blend-back-dpoit', quad_vert, blendBackDpoit_frag);
|
||||
type BlendBackDpoitRenderable = ComputeRenderable<Values<typeof BlendBackDpoitSchema>>
|
||||
|
||||
function getBlendBackDpoitRenderable(ctx: WebGLContext, dopitBlendBackTexture: Texture): BlendBackDpoitRenderable {
|
||||
const values: Values<typeof BlendBackDpoitSchema> = {
|
||||
...QuadValues,
|
||||
tDpoitBackColor: ValueCell.create(dopitBlendBackTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(dopitBlendBackTexture.getWidth(), dopitBlendBackTexture.getHeight())),
|
||||
};
|
||||
|
||||
const schema = { ...BlendBackDpoitSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', BlendBackDpoitShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
const EvaluateDpoitSchema = {
|
||||
...QuadSchema,
|
||||
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
};
|
||||
const EvaluateDpoitShaderCode = ShaderCode('evaluate-dpoit', quad_vert, evaluateDpoit_frag);
|
||||
type EvaluateDpoitRenderable = ComputeRenderable<Values<typeof EvaluateDpoitSchema>>
|
||||
|
||||
function getEvaluateDpoitRenderable(ctx: WebGLContext, dpoitFrontColorTexture: Texture): EvaluateDpoitRenderable {
|
||||
const values: Values<typeof EvaluateDpoitSchema> = {
|
||||
...QuadValues,
|
||||
tDpoitFrontColor: ValueCell.create(dpoitFrontColorTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(dpoitFrontColorTexture.getWidth(), dpoitFrontColorTexture.getHeight())),
|
||||
};
|
||||
|
||||
const schema = { ...EvaluateDpoitSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateDpoitShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
export class DpoitPass {
|
||||
private readonly DEPTH_CLEAR_VALUE = -99999.0; // NOTE same constant is set in shaders
|
||||
private readonly MAX_DEPTH = 1.0;
|
||||
private readonly MIN_DEPTH = 0.0;
|
||||
|
||||
private passCount = 0;
|
||||
private writeId: number;
|
||||
private readId: number;
|
||||
|
||||
private readonly blendBackRenderable: BlendBackDpoitRenderable;
|
||||
private readonly renderable: EvaluateDpoitRenderable;
|
||||
|
||||
private readonly depthFramebuffers: Framebuffer[];
|
||||
private readonly colorFramebuffers: Framebuffer[];
|
||||
|
||||
private readonly depthTextures: Texture[];
|
||||
private readonly colorFrontTextures: Texture[];
|
||||
private readonly colorBackTextures: Texture[];
|
||||
|
||||
private _supported = false;
|
||||
get supported() {
|
||||
return this._supported;
|
||||
}
|
||||
|
||||
bind() {
|
||||
const { state, gl, extensions: { blendMinMax } } = this.webgl;
|
||||
|
||||
// initialize
|
||||
this.passCount = 0;
|
||||
|
||||
this.depthFramebuffers[0].bind();
|
||||
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[1].bind();
|
||||
state.clearColor(-this.MIN_DEPTH, this.MAX_DEPTH, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[0].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[1].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[0].bind();
|
||||
state.blendEquation(blendMinMax!.MAX);
|
||||
state.depthMask(false);
|
||||
|
||||
return {
|
||||
depth: this.depthTextures[1],
|
||||
frontColor: this.colorFrontTextures[1],
|
||||
backColor: this.colorBackTextures[1]
|
||||
};
|
||||
}
|
||||
|
||||
bindDualDepthPeeling() {
|
||||
const { state, gl, extensions: { blendMinMax } } = this.webgl;
|
||||
|
||||
this.readId = this.passCount % 2;
|
||||
this.writeId = 1 - this.readId; // ping-pong: 0 or 1
|
||||
|
||||
this.passCount += 1; // increment for next pass
|
||||
|
||||
this.depthFramebuffers[this.writeId].bind();
|
||||
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[this.writeId].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[this.writeId].bind();
|
||||
state.blendEquation(blendMinMax!.MAX);
|
||||
state.depthMask(false);
|
||||
|
||||
return {
|
||||
depth: this.depthTextures[this.readId],
|
||||
frontColor: this.colorFrontTextures[this.readId],
|
||||
backColor: this.colorBackTextures[this.readId]
|
||||
};
|
||||
}
|
||||
|
||||
renderBlendBack() {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.renderBlendBack');
|
||||
const { state, gl } = this.webgl;
|
||||
|
||||
state.blendEquation(gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ValueCell.update(this.blendBackRenderable.values.tDpoitBackColor, this.colorBackTextures[this.writeId]);
|
||||
|
||||
this.blendBackRenderable.update();
|
||||
this.blendBackRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.renderBlendBack');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.render');
|
||||
const { state, gl } = this.webgl;
|
||||
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ValueCell.update(this.renderable.values.tDpoitFrontColor, this.colorFrontTextures[this.writeId]);
|
||||
|
||||
this.renderable.update();
|
||||
this.renderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.render');
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
if (width !== w || height !== h) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
this.depthTextures[i].define(width, height);
|
||||
this.colorFrontTextures[i].define(width, height);
|
||||
this.colorBackTextures[i].define(width, height);
|
||||
}
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.blendBackRenderable.values.uTexSize, Vec2.set(this.blendBackRenderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this._supported) this._init();
|
||||
}
|
||||
|
||||
private _init() {
|
||||
const { extensions: { drawBuffers } } = this.webgl;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// depth
|
||||
this.depthFramebuffers[i].bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1,
|
||||
drawBuffers!.COLOR_ATTACHMENT2
|
||||
]);
|
||||
|
||||
this.colorFrontTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color0');
|
||||
this.colorBackTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color1');
|
||||
this.depthTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color2');
|
||||
|
||||
// color
|
||||
this.colorFramebuffers[i].bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1
|
||||
]);
|
||||
|
||||
this.colorFrontTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color0');
|
||||
this.colorBackTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color1');
|
||||
}
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, blendMinMax } } = webgl;
|
||||
if (!textureFloat || !colorBufferFloat || !drawBuffers || !blendMinMax) {
|
||||
if (isDebugMode) {
|
||||
const missing: string[] = [];
|
||||
if (!textureFloat) missing.push('textureFloat');
|
||||
if (!colorBufferFloat) missing.push('colorBufferFloat');
|
||||
if (!drawBuffers) missing.push('drawBuffers');
|
||||
if (!blendMinMax) missing.push('blendMinMax');
|
||||
console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!DpoitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
|
||||
|
||||
// textures
|
||||
|
||||
if (isWebGL2(webgl.gl)) {
|
||||
this.depthTextures = [
|
||||
resources.texture('image-float32', 'rg', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rg', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorFrontTextures = colorBufferHalfFloat && textureHalfFloat ? [
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
] : [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorBackTextures = colorBufferHalfFloat && textureHalfFloat ? [
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
] : [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
} else {
|
||||
// in webgl1 drawbuffers must be in the same format for some reason
|
||||
|
||||
this.depthTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorFrontTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorBackTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
}
|
||||
|
||||
this.depthTextures[0].define(width, height);
|
||||
this.depthTextures[1].define(width, height);
|
||||
|
||||
this.colorFrontTextures[0].define(width, height);
|
||||
this.colorFrontTextures[1].define(width, height);
|
||||
|
||||
this.colorBackTextures[0].define(width, height);
|
||||
this.colorBackTextures[1].define(width, height);
|
||||
|
||||
// framebuffers
|
||||
|
||||
this.depthFramebuffers = [resources.framebuffer(), resources.framebuffer()];
|
||||
this.colorFramebuffers = [resources.framebuffer(), resources.framebuffer()];
|
||||
|
||||
// renderables
|
||||
|
||||
this.blendBackRenderable = getBlendBackDpoitRenderable(webgl, this.colorBackTextures[0]);
|
||||
this.renderable = getEvaluateDpoitRenderable(webgl, this.colorFrontTextures[0]);
|
||||
|
||||
this._supported = true;
|
||||
this._init();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 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>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -18,7 +17,6 @@ import { Helper } from '../helper/helper';
|
||||
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
import { WboitPass } from './wboit';
|
||||
import { DpoitPass } from './dpoit';
|
||||
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
|
||||
import { MarkingPass, MarkingProps } from './marking';
|
||||
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
|
||||
@@ -29,7 +27,6 @@ type Props = {
|
||||
postprocessing: PostprocessingProps;
|
||||
marking: MarkingProps;
|
||||
transparentBackground: boolean;
|
||||
dpoitIterations: number;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
@@ -55,7 +52,6 @@ export class DrawPass {
|
||||
private copyFboPostprocessing: CopyRenderable;
|
||||
|
||||
private readonly wboit: WboitPass | undefined;
|
||||
private readonly dpoit: DpoitPass | undefined;
|
||||
private readonly marking: MarkingPass;
|
||||
readonly postprocessing: PostprocessingPass;
|
||||
private readonly antialiasing: AntialiasingPass;
|
||||
@@ -64,12 +60,9 @@ export class DrawPass {
|
||||
return !!this.wboit?.supported;
|
||||
}
|
||||
|
||||
get dpoitEnabled() {
|
||||
return !!this.dpoit?.supported;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean, enableDpoit: boolean) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean) {
|
||||
const { extensions, resources, isWebGL2 } = webgl;
|
||||
|
||||
this.drawTarget = createNullRenderTarget(webgl.gl);
|
||||
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
|
||||
this.packedDepth = !extensions.depthTexture;
|
||||
@@ -85,7 +78,6 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
|
||||
this.dpoit = enableDpoit ? new DpoitPass(webgl, width, height) : undefined;
|
||||
this.marking = new MarkingPass(webgl, width, height);
|
||||
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
|
||||
this.antialiasing = new AntialiasingPass(webgl, this);
|
||||
@@ -96,7 +88,6 @@ export class DrawPass {
|
||||
|
||||
reset() {
|
||||
this.wboit?.reset();
|
||||
this.dpoit?.reset();
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -120,70 +111,12 @@ export class DrawPass {
|
||||
this.wboit.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.dpoit?.supported) {
|
||||
this.dpoit.setSize(width, height);
|
||||
}
|
||||
|
||||
this.marking.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.antialiasing.setSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
if (!this.dpoit?.supported) throw new Error('expected dpoit to be supported');
|
||||
|
||||
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
renderer.clear(true);
|
||||
|
||||
// render opaque primitives
|
||||
if (scene.hasOpaque) {
|
||||
renderer.renderDpoitOpaque(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
if (PostprocessingPass.isEnabled(postprocessingProps)) {
|
||||
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
|
||||
this.depthTargetTransparent.bind();
|
||||
renderer.clearDepth(true);
|
||||
if (scene.opacityAverage < 1) {
|
||||
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
|
||||
}
|
||||
}
|
||||
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
|
||||
}
|
||||
|
||||
this.depthTextureOpaque.detachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
|
||||
// render transparent primitives
|
||||
if (scene.opacityAverage < 1) {
|
||||
const target = PostprocessingPass.isEnabled(postprocessingProps)
|
||||
? this.postprocessing.target : this.colorTarget;
|
||||
|
||||
const dpoitTextures = this.dpoit.bind();
|
||||
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.layer');
|
||||
const dpoitTextures = this.dpoit.bindDualDepthPeeling();
|
||||
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
|
||||
|
||||
target.bind();
|
||||
this.dpoit.renderBlendBack();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.layer');
|
||||
}
|
||||
|
||||
// evaluate dpoit
|
||||
target.bind();
|
||||
this.dpoit.render();
|
||||
}
|
||||
|
||||
// render transparent volumes
|
||||
if (scene.volumes.renderables.length > 0) {
|
||||
renderer.renderDpoitVolume(scene.volumes, camera, this.depthTextureOpaque);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
|
||||
|
||||
@@ -196,7 +129,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
if (PostprocessingPass.isEnabled(postprocessingProps)) {
|
||||
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
|
||||
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
|
||||
this.depthTargetTransparent.bind();
|
||||
renderer.clearDepth(true);
|
||||
if (scene.opacityAverage < 1) {
|
||||
@@ -204,7 +137,7 @@ export class DrawPass {
|
||||
}
|
||||
}
|
||||
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
|
||||
}
|
||||
|
||||
// render transparent primitives and volumes
|
||||
@@ -260,7 +193,7 @@ export class DrawPass {
|
||||
this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
|
||||
}
|
||||
|
||||
if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
|
||||
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
|
||||
this.depthTargetTransparent.bind();
|
||||
renderer.clearDepth(true);
|
||||
if (scene.opacityAverage < 1) {
|
||||
@@ -268,7 +201,7 @@ export class DrawPass {
|
||||
}
|
||||
}
|
||||
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light);
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
|
||||
|
||||
if (!this.packedDepth) {
|
||||
this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
|
||||
@@ -312,7 +245,7 @@ export class DrawPass {
|
||||
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
renderer.setViewport(x, y, width, height);
|
||||
renderer.update(camera, scene);
|
||||
renderer.update(camera);
|
||||
|
||||
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
|
||||
this.drawTarget.bind();
|
||||
@@ -321,15 +254,13 @@ export class DrawPass {
|
||||
|
||||
if (this.wboitEnabled) {
|
||||
this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
|
||||
} else if (this.dpoitEnabled) {
|
||||
this._renderDpoit(renderer, camera, scene, props.dpoitIterations, transparentBackground, props.postprocessing);
|
||||
} else {
|
||||
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
|
||||
}
|
||||
|
||||
const target = postprocessingEnabled
|
||||
? this.postprocessing.target
|
||||
: !toDrawingBuffer || volumeRendering || this.wboitEnabled || this.dpoitEnabled
|
||||
: !toDrawingBuffer || volumeRendering || this.wboitEnabled
|
||||
? this.colorTarget
|
||||
: this.drawTarget;
|
||||
|
||||
@@ -360,7 +291,7 @@ export class DrawPass {
|
||||
}
|
||||
if (helper.camera.isEnabled) {
|
||||
helper.camera.update(camera);
|
||||
renderer.update(helper.camera.camera, helper.camera.scene);
|
||||
renderer.update(helper.camera.camera);
|
||||
renderer.renderBlended(helper.camera.scene, helper.camera.camera);
|
||||
}
|
||||
|
||||
@@ -372,7 +303,7 @@ export class DrawPass {
|
||||
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
|
||||
if (postprocessingEnabled) {
|
||||
this.copyFboPostprocessing.render();
|
||||
} else if (volumeRendering || this.wboitEnabled || this.dpoitEnabled) {
|
||||
} else if (volumeRendering || this.wboitEnabled) {
|
||||
this.copyFboTarget.render();
|
||||
}
|
||||
}
|
||||
@@ -392,12 +323,8 @@ export class DrawPass {
|
||||
renderer.setPixelRatio(this.webgl.pixelRatio);
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
if (isTimingMode) this.webgl.timer.mark('StereoCamera.left');
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.left');
|
||||
if (isTimingMode) this.webgl.timer.mark('StereoCamera.right');
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.right');
|
||||
} else {
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
}
|
||||
@@ -412,4 +339,4 @@ export class DrawPass {
|
||||
}
|
||||
return this.colorTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import { AssetManager } from '../../mol-util/assets';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
marking: PD.Group(MarkingParams),
|
||||
@@ -49,10 +48,10 @@ export class ImagePass {
|
||||
get width() { return this._width; }
|
||||
get height() { return this._height; }
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, enableDpoit: boolean, props: Partial<ImageProps>) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
|
||||
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
|
||||
|
||||
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit, enableDpoit);
|
||||
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit);
|
||||
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
|
||||
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -27,8 +27,6 @@ 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.' }),
|
||||
};
|
||||
@@ -103,10 +101,10 @@ export class MarkingPass {
|
||||
}
|
||||
|
||||
update(props: MarkingProps) {
|
||||
const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength, highlightEdgeStrength, selectEdgeStrength } = props;
|
||||
const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props;
|
||||
|
||||
const { values: edgeValues } = this.edge;
|
||||
const _edgeScale = Math.max(1, Math.round(edgeScale * this.webgl.pixelRatio));
|
||||
const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio);
|
||||
if (edgeValues.dEdgeScale.ref.value !== _edgeScale) {
|
||||
ValueCell.update(edgeValues.dEdgeScale, _edgeScale);
|
||||
this.edge.update();
|
||||
@@ -115,10 +113,8 @@ 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.updateIfChanged(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
|
||||
ValueCell.updateIfChanged(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
|
||||
ValueCell.updateIfChanged(overlayValues.uHighlightEdgeStrength, highlightEdgeStrength);
|
||||
ValueCell.updateIfChanged(overlayValues.uSelectEdgeStrength, selectEdgeStrength);
|
||||
ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
|
||||
ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
|
||||
}
|
||||
|
||||
render(viewport: Viewport, target: RenderTarget | undefined) {
|
||||
@@ -174,8 +170,6 @@ const OverlaySchema = {
|
||||
uTexSizeInv: UniformSpec('v2'),
|
||||
uHighlightEdgeColor: UniformSpec('v3'),
|
||||
uSelectEdgeColor: UniformSpec('v3'),
|
||||
uHighlightEdgeStrength: UniformSpec('f'),
|
||||
uSelectEdgeStrength: UniformSpec('f'),
|
||||
uGhostEdgeStrength: UniformSpec('f'),
|
||||
uInnerEdgeFactor: UniformSpec('f'),
|
||||
};
|
||||
@@ -192,8 +186,6 @@ 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),
|
||||
};
|
||||
|
||||
@@ -61,7 +61,6 @@ type Props = {
|
||||
postprocessing: PostprocessingProps
|
||||
marking: MarkingProps
|
||||
transparentBackground: boolean;
|
||||
dpoitIterations: number;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
|
||||
@@ -15,19 +15,16 @@ export class Passes {
|
||||
readonly pick: PickPass;
|
||||
readonly multiSample: MultiSamplePass;
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean, enableDpoit: boolean }> = {}) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
|
||||
const { gl } = webgl;
|
||||
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false, attribs.enableDpoit || false);
|
||||
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
|
||||
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
|
||||
this.multiSample = new MultiSamplePass(webgl, this.draw);
|
||||
}
|
||||
|
||||
updateSize() {
|
||||
const { gl } = this.webgl;
|
||||
// Avoid setting dimensions to 0x0 because it causes "empty textures are not allowed" error.
|
||||
const width = Math.max(gl.drawingBufferWidth, 2);
|
||||
const height = Math.max(gl.drawingBufferHeight, 2);
|
||||
this.draw.setSize(width, height);
|
||||
this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.pick.syncSize();
|
||||
this.multiSample.syncSize();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 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>
|
||||
*/
|
||||
@@ -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, scene);
|
||||
renderer.update(camera);
|
||||
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, helper.camera.scene);
|
||||
renderer.update(helper.camera.camera);
|
||||
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', true);
|
||||
if (isTimingMode) this.webgl.timer.mark('PickHelper.render');
|
||||
const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this;
|
||||
const { renderer, scene, helper } = this;
|
||||
|
||||
@@ -358,7 +358,7 @@ export class PickHelper {
|
||||
|
||||
const z = this.getDepth(xp, yp);
|
||||
// console.log('z', z);
|
||||
const position = Vec3.create(x, y, z);
|
||||
const position = Vec3.create(x, viewport.height - y, z);
|
||||
if (StereoCamera.is(camera)) {
|
||||
const halfWidth = Math.floor(viewport.width / 2);
|
||||
if (x > viewport.x + halfWidth) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 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>
|
||||
* @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';
|
||||
@@ -11,7 +10,7 @@ import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/rende
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { deepEqual, ValueCell } from '../../mol-util';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
|
||||
@@ -31,8 +30,6 @@ 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,
|
||||
@@ -43,14 +40,12 @@ const OutlinesSchema = {
|
||||
dOrthographic: DefineSpec('number'),
|
||||
uNear: UniformSpec('f'),
|
||||
uFar: UniformSpec('f'),
|
||||
uInvProjection: UniformSpec('m4'),
|
||||
|
||||
uOutlineThreshold: UniformSpec('f'),
|
||||
dTransparentOutline: DefineSpec('boolean'),
|
||||
uMaxPossibleViewZDiff: UniformSpec('f'),
|
||||
};
|
||||
type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
|
||||
|
||||
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
|
||||
function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture): OutlinesRenderable {
|
||||
const width = depthTextureOpaque.getWidth();
|
||||
const height = depthTextureOpaque.getHeight();
|
||||
|
||||
@@ -63,10 +58,8 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
|
||||
dOrthographic: ValueCell.create(0),
|
||||
uNear: ValueCell.create(1),
|
||||
uFar: ValueCell.create(10000),
|
||||
uInvProjection: ValueCell.create(Mat4.identity()),
|
||||
|
||||
uOutlineThreshold: ValueCell.create(0.33),
|
||||
dTransparentOutline: ValueCell.create(transparentOutline),
|
||||
uMaxPossibleViewZDiff: ValueCell.create(0.5),
|
||||
};
|
||||
|
||||
const schema = { ...OutlinesSchema };
|
||||
@@ -76,69 +69,9 @@ 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'),
|
||||
tDepthHalf: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
tDepthQuarter: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
|
||||
uSamples: UniformSpec('v3[]'),
|
||||
dNSamples: DefineSpec('number'),
|
||||
@@ -151,23 +84,14 @@ const SsaoSchema = {
|
||||
|
||||
uRadius: UniformSpec('f'),
|
||||
uBias: UniformSpec('f'),
|
||||
|
||||
dMultiScale: DefineSpec('boolean'),
|
||||
dLevels: DefineSpec('number'),
|
||||
uLevelRadius: UniformSpec('f[]'),
|
||||
uLevelBias: UniformSpec('f[]'),
|
||||
uNearThreshold: UniformSpec('f'),
|
||||
uFarThreshold: UniformSpec('f'),
|
||||
};
|
||||
|
||||
type SsaoRenderable = ComputeRenderable<Values<typeof SsaoSchema>>
|
||||
|
||||
function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture, depthHalfTexture: Texture, depthQuarterTexture: Texture): SsaoRenderable {
|
||||
function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRenderable {
|
||||
const values: Values<typeof SsaoSchema> = {
|
||||
...QuadValues,
|
||||
tDepth: ValueCell.create(depthTexture),
|
||||
tDepthHalf: ValueCell.create(depthHalfTexture),
|
||||
tDepthQuarter: ValueCell.create(depthQuarterTexture),
|
||||
|
||||
uSamples: ValueCell.create(getSamples(32)),
|
||||
dNSamples: ValueCell.create(32),
|
||||
@@ -178,15 +102,8 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture, depthHalfTe
|
||||
|
||||
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
|
||||
|
||||
uRadius: ValueCell.create(Math.pow(2, 5)),
|
||||
uBias: ValueCell.create(0.8),
|
||||
|
||||
dMultiScale: ValueCell.create(false),
|
||||
dLevels: ValueCell.create(3),
|
||||
uLevelRadius: ValueCell.create([Math.pow(2, 2), Math.pow(2, 5), Math.pow(2, 8)]),
|
||||
uLevelBias: ValueCell.create([0.8, 0.8, 0.8]),
|
||||
uNearThreshold: ValueCell.create(10.0),
|
||||
uFarThreshold: ValueCell.create(1500.0),
|
||||
uRadius: ValueCell.create(8.0),
|
||||
uBias: ValueCell.create(0.025),
|
||||
};
|
||||
|
||||
const schema = { ...SsaoSchema };
|
||||
@@ -207,7 +124,8 @@ const SsaoBlurSchema = {
|
||||
uBlurDirectionX: UniformSpec('f'),
|
||||
uBlurDirectionY: UniformSpec('f'),
|
||||
|
||||
uInvProjection: UniformSpec('m4'),
|
||||
uMaxPossibleViewZDiff: UniformSpec('f'),
|
||||
|
||||
uNear: UniformSpec('f'),
|
||||
uFar: UniformSpec('f'),
|
||||
uBounds: UniformSpec('v4'),
|
||||
@@ -228,7 +146,8 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
|
||||
uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
|
||||
uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
|
||||
|
||||
uInvProjection: ValueCell.create(Mat4.identity()),
|
||||
uMaxPossibleViewZDiff: ValueCell.create(0.5),
|
||||
|
||||
uNear: ValueCell.create(0.0),
|
||||
uFar: ValueCell.create(10000.0),
|
||||
uBounds: ValueCell.create(Vec4()),
|
||||
@@ -285,7 +204,6 @@ 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'),
|
||||
|
||||
@@ -296,28 +214,26 @@ const PostprocessingSchema = {
|
||||
uFogFar: UniformSpec('f'),
|
||||
uFogColor: UniformSpec('v3'),
|
||||
uOutlineColor: UniformSpec('v3'),
|
||||
uOcclusionColor: UniformSpec('v3'),
|
||||
uTransparentBackground: UniformSpec('b'),
|
||||
|
||||
uMaxPossibleViewZDiff: UniformSpec('f'),
|
||||
|
||||
dOcclusionEnable: DefineSpec('boolean'),
|
||||
uOcclusionOffset: UniformSpec('v2'),
|
||||
|
||||
dShadowEnable: DefineSpec('boolean'),
|
||||
|
||||
dOutlineEnable: DefineSpec('boolean'),
|
||||
dOutlineScale: DefineSpec('number'),
|
||||
dTransparentOutline: DefineSpec('boolean'),
|
||||
uOutlineThreshold: UniformSpec('f'),
|
||||
};
|
||||
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
|
||||
|
||||
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture, transparentOutline: boolean): PostprocessingRenderable {
|
||||
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): 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())),
|
||||
|
||||
@@ -328,17 +244,16 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uFogFar: ValueCell.create(10000),
|
||||
uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
|
||||
uOutlineColor: ValueCell.create(Vec3.create(0, 0, 0)),
|
||||
uOcclusionColor: ValueCell.create(Vec3.create(0, 0, 0)),
|
||||
uTransparentBackground: ValueCell.create(false),
|
||||
|
||||
uMaxPossibleViewZDiff: ValueCell.create(0.5),
|
||||
|
||||
dOcclusionEnable: ValueCell.create(true),
|
||||
uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
|
||||
|
||||
dShadowEnable: ValueCell.create(false),
|
||||
|
||||
dOutlineEnable: ValueCell.create(false),
|
||||
dOutlineScale: ValueCell.create(1),
|
||||
dTransparentOutline: ValueCell.create(transparentOutline),
|
||||
uOutlineThreshold: ValueCell.create(0.33),
|
||||
};
|
||||
|
||||
const schema = { ...PostprocessingSchema };
|
||||
@@ -352,45 +267,18 @@ export const PostprocessingParams = {
|
||||
occlusion: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
|
||||
multiScale: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
levels: PD.ObjectList({
|
||||
radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
|
||||
bias: PD.Numeric(1, { min: 0, max: 3, step: 0.1 }),
|
||||
}, o => `${o.radius}, ${o.bias}`, { defaultValue: [
|
||||
{ radius: 2, bias: 1 },
|
||||
{ radius: 5, bias: 1 },
|
||||
{ radius: 8, bias: 1 },
|
||||
{ radius: 11, bias: 1 },
|
||||
] }),
|
||||
nearThreshold: PD.Numeric(10, { min: 0, max: 50, step: 1 }),
|
||||
farThreshold: PD.Numeric(1500, { min: 0, max: 10000, step: 100 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x', hideIf: p => p?.multiScale.name === 'on' }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
|
||||
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
|
||||
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
|
||||
resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
|
||||
color: PD.Color(Color(0x000000)),
|
||||
}),
|
||||
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' }),
|
||||
@@ -401,37 +289,15 @@ 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>
|
||||
|
||||
type Levels = {
|
||||
count: number
|
||||
radius: number[]
|
||||
bias: number[]
|
||||
}
|
||||
|
||||
function getLevels(props: { radius: number, bias: number }[], levels?: Levels): Levels {
|
||||
const count = props.length;
|
||||
const { radius, bias } = levels || {
|
||||
radius: (new Array(count * 3)).fill(0),
|
||||
bias: (new Array(count * 3)).fill(0),
|
||||
};
|
||||
props = props.slice().sort((a, b) => a.radius - b.radius);
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const p = props[i];
|
||||
radius[i] = Math.pow(2, p.radius);
|
||||
bias[i] = p.bias;
|
||||
}
|
||||
return { count, radius, bias };
|
||||
}
|
||||
|
||||
export class PostprocessingPass {
|
||||
static isEnabled(props: PostprocessingProps) {
|
||||
return props.occlusion.name === 'on' || props.shadow.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
|
||||
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
|
||||
}
|
||||
|
||||
static isTransparentOutlineEnabled(props: PostprocessingProps) {
|
||||
return props.outline.name === 'on' && props.outline.params.includeTransparent;
|
||||
static isOutlineEnabled(props: PostprocessingProps) {
|
||||
return props.outline.name === 'on';
|
||||
}
|
||||
|
||||
readonly target: RenderTarget;
|
||||
@@ -439,9 +305,6 @@ 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;
|
||||
@@ -449,12 +312,6 @@ export class PostprocessingPass {
|
||||
private readonly downsampledDepthTarget: RenderTarget;
|
||||
private readonly downsampleDepthRenderable: CopyRenderable;
|
||||
|
||||
private readonly depthHalfTarget: RenderTarget;
|
||||
private readonly depthHalfRenderable: CopyRenderable;
|
||||
|
||||
private readonly depthQuarterTarget: RenderTarget;
|
||||
private readonly depthQuarterRenderable: CopyRenderable;
|
||||
|
||||
private readonly ssaoDepthTexture: Texture;
|
||||
private readonly ssaoDepthBlurProxyTexture: Texture;
|
||||
|
||||
@@ -474,8 +331,6 @@ export class PostprocessingPass {
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
|
||||
}
|
||||
|
||||
private levels: { radius: number, bias: number }[];
|
||||
|
||||
private readonly bgColor = Vec3();
|
||||
readonly background: BackgroundPass;
|
||||
|
||||
@@ -488,16 +343,12 @@ export class PostprocessingPass {
|
||||
this.blurKernelSize = 1;
|
||||
this.downsampleFactor = 1;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
this.levels = [];
|
||||
|
||||
// needs to be linear for anti-aliasing pass
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
|
||||
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
|
||||
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent, true);
|
||||
|
||||
this.shadowsTarget = webgl.createRenderTarget(width, height, false);
|
||||
this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
|
||||
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
|
||||
|
||||
this.ssaoFramebuffer = webgl.resources.framebuffer();
|
||||
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
|
||||
@@ -506,27 +357,9 @@ export class PostprocessingPass {
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
|
||||
const hw = Math.floor(sw * 0.5);
|
||||
const hh = Math.floor(sh * 0.5);
|
||||
|
||||
const qw = Math.floor(sw * 0.25);
|
||||
const qh = Math.floor(sh * 0.25);
|
||||
|
||||
this.downsampledDepthTarget = drawPass.packedDepth
|
||||
? webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear', 'rgba')
|
||||
: webgl.createRenderTarget(sw, sh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
|
||||
this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
|
||||
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
|
||||
|
||||
this.depthHalfTarget = drawPass.packedDepth
|
||||
? webgl.createRenderTarget(hw, hh, false, 'uint8', 'linear', 'rgba')
|
||||
: webgl.createRenderTarget(hw, hh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
|
||||
this.depthHalfRenderable = createCopyRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
|
||||
|
||||
this.depthQuarterTarget = drawPass.packedDepth
|
||||
? webgl.createRenderTarget(qw, qh, false, 'uint8', 'linear', 'rgba')
|
||||
: webgl.createRenderTarget(qw, qh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
|
||||
this.depthQuarterRenderable = createCopyRenderable(webgl, this.depthHalfTarget.texture);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
@@ -537,10 +370,10 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture, this.depthHalfTarget.texture, this.depthQuarterTarget.texture);
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 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.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture);
|
||||
|
||||
this.background = new BackgroundPass(webgl, assetManager, width, height);
|
||||
}
|
||||
@@ -552,30 +385,17 @@ export class PostprocessingPass {
|
||||
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
|
||||
this.ssaoScale = ssaoScale;
|
||||
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.shadowsTarget.setSize(width, height);
|
||||
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
const hw = Math.floor(sw * 0.5);
|
||||
const hh = Math.floor(sh * 0.5);
|
||||
this.depthHalfTarget.setSize(hw, hh);
|
||||
|
||||
const qw = Math.floor(sw * 0.25);
|
||||
const qh = Math.floor(sh * 0.25);
|
||||
this.depthQuarterTarget.setSize(qw, qh);
|
||||
|
||||
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.depthHalfRenderable.values.uTexSize, Vec2.set(this.depthHalfRenderable.values.uTexSize.ref.value, hw, hh));
|
||||
ValueCell.update(this.depthQuarterRenderable.values.uTexSize, Vec2.set(this.depthQuarterRenderable.values.uTexSize.ref.value, qw, qh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
@@ -584,29 +404,25 @@ export class PostprocessingPass {
|
||||
}
|
||||
}
|
||||
|
||||
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
|
||||
let needsUpdateShadows = false;
|
||||
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
|
||||
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),
|
||||
@@ -624,14 +440,11 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
|
||||
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uInvProjection, invProjection);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uInvProjection, invProjection);
|
||||
|
||||
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
|
||||
needsUpdateSsaoBlur = true;
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
|
||||
}
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
|
||||
|
||||
if (this.nSamples !== props.occlusion.params.samples) {
|
||||
needsUpdateSsao = true;
|
||||
@@ -640,30 +453,7 @@ export class PostprocessingPass {
|
||||
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.nSamples));
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
|
||||
}
|
||||
|
||||
const multiScale = props.occlusion.params.multiScale.name === 'on';
|
||||
if (this.ssaoRenderable.values.dMultiScale.ref.value !== multiScale) {
|
||||
needsUpdateSsao = true;
|
||||
ValueCell.update(this.ssaoRenderable.values.dMultiScale, multiScale);
|
||||
}
|
||||
|
||||
if (props.occlusion.params.multiScale.name === 'on') {
|
||||
const mp = props.occlusion.params.multiScale.params;
|
||||
if (!deepEqual(this.levels, mp.levels)) {
|
||||
needsUpdateSsao = true;
|
||||
|
||||
this.levels = mp.levels;
|
||||
const levels = getLevels(mp.levels);
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.dLevels, levels.count);
|
||||
|
||||
ValueCell.update(this.ssaoRenderable.values.uLevelRadius, levels.radius);
|
||||
ValueCell.update(this.ssaoRenderable.values.uLevelBias, levels.bias);
|
||||
}
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uNearThreshold, mp.nearThreshold);
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uFarThreshold, mp.farThreshold);
|
||||
} else {
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
|
||||
}
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias);
|
||||
|
||||
if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) {
|
||||
@@ -674,8 +464,8 @@ export class PostprocessingPass {
|
||||
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
}
|
||||
|
||||
if (this.downsampleFactor !== props.occlusion.params.resolutionScale) {
|
||||
@@ -686,101 +476,42 @@ export class PostprocessingPass {
|
||||
|
||||
const sw = Math.floor(w * this.ssaoScale);
|
||||
const sh = Math.floor(h * this.ssaoScale);
|
||||
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
const hw = Math.floor(sw * 0.5);
|
||||
const hh = Math.floor(sh * 0.5);
|
||||
this.depthHalfTarget.setSize(hw, hh);
|
||||
|
||||
const qw = Math.floor(sw * 0.25);
|
||||
const qh = Math.floor(sh * 0.25);
|
||||
this.depthQuarterTarget.setSize(qw, qh);
|
||||
|
||||
if (this.ssaoScale === 1) {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureOpaque);
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureTransparent);
|
||||
} else {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
|
||||
}
|
||||
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepthHalf, this.depthHalfTarget.texture);
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepthQuarter, this.depthQuarterTarget.texture);
|
||||
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.depthHalfRenderable.values.uTexSize, Vec2.set(this.depthHalfRenderable.values.uTexSize.ref.value, hw, hh));
|
||||
ValueCell.update(this.depthQuarterRenderable.values.uTexSize, Vec2.set(this.depthQuarterRenderable.values.uTexSize.ref.value, qw, qh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
}
|
||||
|
||||
ValueCell.update(this.renderable.values.uOcclusionColor, Color.toVec3Normalized(this.renderable.values.uOcclusionColor.ref.value, props.occlusion.params.color));
|
||||
}
|
||||
|
||||
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);
|
||||
if (this.shadowsRenderable.values.dOrthographic.ref.value !== orthographic) {
|
||||
ValueCell.update(this.shadowsRenderable.values.dOrthographic, orthographic);
|
||||
needsUpdateShadows = true;
|
||||
}
|
||||
|
||||
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') {
|
||||
const transparentOutline = props.outline.params.includeTransparent ?? true;
|
||||
let { threshold } = props.outline.params;
|
||||
// orthographic needs lower threshold
|
||||
if (camera.state.mode === 'orthographic') threshold /= 5;
|
||||
const factor = Math.pow(1000, threshold) / 1000;
|
||||
// use radiusMax for stable outlines when zooming
|
||||
const maxPossibleViewZDiff = factor * camera.state.radiusMax;
|
||||
const outlineScale = props.outline.params.scale - 1;
|
||||
const outlineThreshold = 50 * props.outline.params.threshold;
|
||||
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
|
||||
ValueCell.update(this.outlinesRenderable.values.uInvProjection, invProjection);
|
||||
if (this.outlinesRenderable.values.dTransparentOutline.ref.value !== transparentOutline) {
|
||||
needsUpdateOutlines = true;
|
||||
ValueCell.update(this.outlinesRenderable.values.dTransparentOutline, transparentOutline);
|
||||
}
|
||||
if (this.outlinesRenderable.values.dOrthographic.ref.value !== orthographic) {
|
||||
needsUpdateOutlines = true;
|
||||
ValueCell.update(this.outlinesRenderable.values.dOrthographic, orthographic);
|
||||
}
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uOutlineThreshold, outlineThreshold);
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
|
||||
|
||||
ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
|
||||
|
||||
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) {
|
||||
needsUpdateMain = true;
|
||||
ValueCell.update(this.renderable.values.dOutlineScale, outlineScale);
|
||||
}
|
||||
if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) {
|
||||
needsUpdateMain = true;
|
||||
ValueCell.update(this.renderable.values.dTransparentOutline, transparentOutline);
|
||||
}
|
||||
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
|
||||
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
|
||||
}
|
||||
|
||||
ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
|
||||
@@ -789,31 +520,12 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.renderable.values.uFogNear, camera.fogNear);
|
||||
ValueCell.update(this.renderable.values.uFogColor, Color.toVec3Normalized(this.renderable.values.uFogColor.ref.value, backgroundColor));
|
||||
ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
|
||||
if (this.renderable.values.dOrthographic.ref.value !== orthographic) {
|
||||
needsUpdateMain = true;
|
||||
ValueCell.update(this.renderable.values.dOrthographic, orthographic);
|
||||
}
|
||||
|
||||
if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) {
|
||||
needsUpdateMain = true;
|
||||
ValueCell.update(this.renderable.values.dOutlineEnable, outlinesEnabled);
|
||||
}
|
||||
if (this.renderable.values.dShadowEnable.ref.value !== shadowsEnabled) {
|
||||
needsUpdateMain = true;
|
||||
ValueCell.update(this.renderable.values.dShadowEnable, shadowsEnabled);
|
||||
}
|
||||
if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) {
|
||||
needsUpdateMain = true;
|
||||
ValueCell.update(this.renderable.values.dOcclusionEnable, occlusionEnabled);
|
||||
}
|
||||
|
||||
if (needsUpdateOutlines) {
|
||||
this.outlinesRenderable.update();
|
||||
}
|
||||
|
||||
if (needsUpdateShadows) {
|
||||
this.shadowsRenderable.update();
|
||||
}
|
||||
if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic);
|
||||
if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled);
|
||||
if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
|
||||
|
||||
if (needsUpdateSsao) {
|
||||
this.ssaoRenderable.update();
|
||||
@@ -834,6 +546,10 @@ export class PostprocessingPass {
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
state.viewport(x, y, width, height);
|
||||
state.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
private occlusionOffset: [x: number, y: number] = [0, 0];
|
||||
@@ -848,42 +564,23 @@ export class PostprocessingPass {
|
||||
this.transparentBackground = value;
|
||||
}
|
||||
|
||||
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
|
||||
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
|
||||
if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render');
|
||||
this.updateState(camera, transparentBackground, backgroundColor, props, light);
|
||||
this.updateState(camera, transparentBackground, backgroundColor, props);
|
||||
|
||||
const { gl, state } = this.webgl;
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
if (props.outline.name === 'on') {
|
||||
this.outlinesTarget.bind();
|
||||
this.outlinesRenderable.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) {
|
||||
if (isTimingMode) this.webgl.timer.mark('SSAO.render');
|
||||
const sx = Math.floor(x * this.ssaoScale);
|
||||
const sy = Math.floor(y * this.ssaoScale);
|
||||
const sw = Math.ceil(width * this.ssaoScale);
|
||||
const sh = Math.ceil(height * this.ssaoScale);
|
||||
|
||||
state.viewport(sx, sy, sw, sh);
|
||||
state.scissor(sx, sy, sw, sh);
|
||||
|
||||
if (this.ssaoScale < 1) {
|
||||
if (isTimingMode) this.webgl.timer.mark('SSAO.downsample');
|
||||
this.downsampledDepthTarget.bind();
|
||||
this.downsampleDepthRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('SSAO.downsample');
|
||||
}
|
||||
|
||||
if (isTimingMode) this.webgl.timer.mark('SSAO.half');
|
||||
this.depthHalfTarget.bind();
|
||||
this.depthHalfRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('SSAO.half');
|
||||
|
||||
if (isTimingMode) this.webgl.timer.mark('SSAO.quarter');
|
||||
this.depthQuarterTarget.bind();
|
||||
this.depthQuarterRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('SSAO.quarter');
|
||||
|
||||
this.ssaoFramebuffer.bind();
|
||||
this.ssaoRenderable.render();
|
||||
|
||||
@@ -892,20 +589,6 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoBlurSecondPassFramebuffer.bind();
|
||||
this.ssaoBlurSecondPassRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('SSAO.render');
|
||||
}
|
||||
|
||||
state.viewport(x, y, width, height);
|
||||
state.scissor(x, y, width, height);
|
||||
|
||||
if (props.outline.name === 'on') {
|
||||
this.outlinesTarget.bind();
|
||||
this.outlinesRenderable.render();
|
||||
}
|
||||
|
||||
if (props.shadow.name === 'on') {
|
||||
this.shadowsTarget.bind();
|
||||
this.shadowsRenderable.render();
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
@@ -914,6 +597,8 @@ export class PostprocessingPass {
|
||||
this.target.bind();
|
||||
}
|
||||
|
||||
const { gl, state } = this.webgl;
|
||||
|
||||
this.background.update(camera, props.background);
|
||||
if (this.background.isEnabled(props.background)) {
|
||||
if (this.transparentBackground) {
|
||||
@@ -999,3 +684,4 @@ export class AntialiasingPass {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,7 +11,7 @@ import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { loadImageTexture, Texture } from '../../mol-gl/webgl/texture';
|
||||
import { createTexture, loadImageTexture, Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Vec2, Vec4 } from '../../mol-math/linear-algebra';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
@@ -74,7 +74,6 @@ export class SmaaPass {
|
||||
state.viewport(x, y, width, height);
|
||||
state.scissor(x, y, width, height);
|
||||
|
||||
state.colorMask(true, true, true, true);
|
||||
state.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
@@ -192,8 +191,8 @@ function getWeightsRenderable(ctx: WebGLContext, edgesTexture: Texture): Weights
|
||||
const width = edgesTexture.getWidth();
|
||||
const height = edgesTexture.getHeight();
|
||||
|
||||
const areaTexture = ctx.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
|
||||
const searchTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
const areaTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgb', 'ubyte', 'linear');
|
||||
const searchTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
|
||||
const values: Values<typeof WeightsSchema> = {
|
||||
...QuadValues,
|
||||
|
||||
@@ -18,8 +18,6 @@ import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag';
|
||||
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../mol-gl/webgl/compat';
|
||||
import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
|
||||
|
||||
const EvaluateWboitSchema = {
|
||||
...QuadSchema,
|
||||
@@ -52,7 +50,6 @@ export class WboitPass {
|
||||
private readonly framebuffer: Framebuffer;
|
||||
private readonly textureA: Texture;
|
||||
private readonly textureB: Texture;
|
||||
private readonly depthRenderbuffer: Renderbuffer;
|
||||
|
||||
private _supported = false;
|
||||
get supported() {
|
||||
@@ -90,7 +87,6 @@ export class WboitPass {
|
||||
if (width !== w || height !== h) {
|
||||
this.textureA.define(width, height);
|
||||
this.textureB.define(width, height);
|
||||
this.depthRenderbuffer.setSize(width, height);
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
@@ -110,8 +106,6 @@ export class WboitPass {
|
||||
|
||||
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
|
||||
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
|
||||
|
||||
this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
@@ -134,7 +128,7 @@ export class WboitPass {
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!WboitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources, gl } = webgl;
|
||||
const { resources } = webgl;
|
||||
|
||||
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureA.define(width, height);
|
||||
@@ -142,10 +136,6 @@ export class WboitPass {
|
||||
this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureB.define(width, height);
|
||||
|
||||
this.depthRenderbuffer = isWebGL2(gl)
|
||||
? resources.renderbuffer('depth32f', 'depth', width, height)
|
||||
: resources.renderbuffer('depth16', 'depth', width, height);
|
||||
|
||||
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
|
||||
this.framebuffer = resources.framebuffer();
|
||||
|
||||
|
||||
@@ -93,35 +93,20 @@ namespace Column {
|
||||
return !!v && !!(v as Column<any>).schema && !!(v as Column<any>).value;
|
||||
}
|
||||
|
||||
// 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) */
|
||||
export const enum ValueKind {
|
||||
Present = 0,
|
||||
/** Expressed in CIF as `.` (= 1) */
|
||||
/** Expressed in CIF as `.` */
|
||||
NotPresent = 1,
|
||||
/** Expressed in CIF as `?` (= 2) */
|
||||
/** Expressed in CIF as `?` */
|
||||
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, ValueKinds.NotPresent);
|
||||
return constColumn(schema['T'], rowCount, schema, ValueKind.NotPresent);
|
||||
}
|
||||
|
||||
export function ofConst<T extends Schema>(v: T['T'], rowCount: number, type: T): Column<T['T']> {
|
||||
return constColumn(v, rowCount, type, ValueKinds.Present);
|
||||
return constColumn(v, rowCount, type, ValueKind.Present);
|
||||
}
|
||||
|
||||
export function ofLambda<T extends Schema>(spec: LambdaSpec<T>): Column<T['T']> {
|
||||
@@ -271,7 +256,7 @@ function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schem
|
||||
return {
|
||||
schema: schema,
|
||||
__array: void 0,
|
||||
isDefined: valueKind === Column.ValueKinds.Present,
|
||||
isDefined: valueKind === Column.ValueKind.Present,
|
||||
rowCount,
|
||||
value,
|
||||
valueKind: row => valueKind,
|
||||
@@ -291,7 +276,7 @@ function lambdaColumn<T extends Column.Schema>({ value, valueKind, areValuesEqua
|
||||
isDefined: true,
|
||||
rowCount,
|
||||
value,
|
||||
valueKind: valueKind ? valueKind : row => Column.ValueKinds.Present,
|
||||
valueKind: valueKind ? valueKind : row => Column.ValueKind.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);
|
||||
@@ -319,7 +304,7 @@ function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Colu
|
||||
isDefined: true,
|
||||
rowCount,
|
||||
value,
|
||||
valueKind: valueKind ? valueKind : row => Column.ValueKinds.Present,
|
||||
valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
|
||||
toArray: schema.valueType === 'str'
|
||||
? (schema as Column.Schema.Str).transform === 'lowercase'
|
||||
? params => {
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Table {
|
||||
rowCount,
|
||||
schema: schema[k],
|
||||
value: r => rows[r][k],
|
||||
valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKinds.NotPresent : Column.ValueKinds.Present
|
||||
valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.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.ValueKinds.Present) {
|
||||
if (c.valueKind(r) === Column.ValueKind.Present) {
|
||||
StringBuilder.write(sb, c.value(r));
|
||||
StringBuilder.write(sb, '|');
|
||||
} else {
|
||||
|
||||
@@ -132,8 +132,8 @@ export namespace BaseGeometry {
|
||||
ValueCell.updateIfChanged(values.uBumpiness, props.material.bumpiness);
|
||||
|
||||
const clip = Clip.getClip(props.clip);
|
||||
ValueCell.updateIfChanged(values.dClipObjectCount, clip.objects.count);
|
||||
ValueCell.updateIfChanged(values.dClipVariant, clip.variant);
|
||||
ValueCell.update(values.dClipObjectCount, clip.objects.count);
|
||||
ValueCell.update(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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -158,7 +158,6 @@ 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),
|
||||
};
|
||||
@@ -214,7 +213,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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('cylinders'),
|
||||
@@ -246,7 +245,6 @@ 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),
|
||||
};
|
||||
@@ -265,14 +263,13 @@ 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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020 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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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 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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 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 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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
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, 0);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user