Compare commits

...

127 Commits

Author SHA1 Message Date
Alexander Rose
6f12f714d2 3.0.0-dev.0 2021-11-28 14:02:46 -08:00
Alexander Rose
1f67077400 changelog 2021-11-28 13:56:07 -08:00
Alexander Rose
d1c4cf69cb Merge pull request #291 from molstar/lighting
Lighting
2021-11-28 13:53:42 -08:00
Alexander Rose
803c5eaa15 changelog 2021-11-28 13:39:53 -08:00
Alexander Rose
970fd5d9c3 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-28 13:32:56 -08:00
Alexander Rose
7ccd4a1e0d 2.4.1 2021-11-28 13:31:12 -08:00
Alexander Rose
eb41882c56 Merge branch 'master' of https://github.com/molstar/molstar 2021-11-28 13:25:42 -08:00
Alexander Rose
734851a810 changelog 2021-11-28 12:46:12 -08:00
Alexander Rose
6318717a15 tweak material param 2021-11-28 12:42:33 -08:00
dsehnal
d8498feaef updade npmignore 2021-11-28 14:36:54 +01:00
Alexander Rose
bce959195a fix renderer spec baseline 2021-11-27 17:31:42 -08:00
Alexander Rose
4287e09a9a fix material param number formating 2021-11-27 17:25:51 -08:00
Alexander Rose
fdc006f833 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-27 17:08:56 -08:00
Alexander Rose
c704b7505c add Substance theming (per-group material) 2021-11-27 17:06:55 -08:00
Alexander Rose
ceaf238322 material improvements
- material helpers
- material in structure component manager
2021-11-27 17:02:48 -08:00
Alexander Rose
b7224ce5c7 lighting tweaks 2021-11-26 18:04:33 -08:00
Alexander Rose
95654175fe improve 'rounded' tube geometry
- correct normals
- circle offset
2021-11-26 15:34:16 -08:00
Alexander Rose
de96244706 allow atoms in aromatic rings to do hydrogen bonds 2021-11-26 14:37:50 -08:00
Alexander Rose
3104ee5742 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-25 15:00:19 -08:00
Alexander Rose
73ac445a44 2.4.0 2021-11-25 14:46:10 -08:00
Alexander Rose
1a1d1d9d30 changelog 2021-11-25 14:41:19 -08:00
Alexander Rose
062aff76da update schemas 2021-11-25 14:40:35 -08:00
Alexander Rose
7d0d24b66d update packages 2021-11-25 14:40:26 -08:00
Alexander Rose
6655672d11 Merge pull request #290 from molstar/smoothing2
Smooth Overpaint and Transparency
2021-11-25 14:13:10 -08:00
Alexander Rose
6e573ae410 reduce args counts in geo exporters 2021-11-25 14:04:58 -08:00
Alexander Rose
1c48c02473 basic overpaint for direct-volume isosurface 2021-11-24 19:49:42 -08:00
Alexander Rose
78be3320ce geo export support smoothed overpaint/transparency 2021-11-24 19:49:05 -08:00
Alexander Rose
c8018800cc grid-based smoothing of Overpaint and Transparency 2021-11-24 19:47:07 -08:00
Alexander Rose
bb795aca98 refactor grid-based color smoothing
- support rgba and alpha values
- CPU and GPU versions
- for Mesh and TextureMesh
2021-11-24 19:43:15 -08:00
Alexander Rose
2cb1279f4c gl compute utils improvements
- CopyRenderable
- readTexture and readAlphaTexture helpers
2021-11-24 19:10:15 -08:00
Alexander Rose
b876c6f618 avoid unnecessary representation state updates 2021-11-24 18:51:16 -08:00
Alexander Rose
3a7dfc055e add Representation.geometryVersion
- increments whenever the geometry of any visual changes
2021-11-24 18:49:10 -08:00
Alexander Rose
928e521ac7 improve handling of .meta in Mesh & TextureMesh 2021-11-24 18:36:04 -08:00
Alexander Rose
e5e9598e4b changelog 2021-11-24 18:31:08 -08:00
Alexander Rose
e6e1809592 Fix secondary-structure property handling
- StructureElement.Property was incorrectly resolving type & key
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
2021-11-24 18:30:53 -08:00
Alexander Rose
812f97ddb7 skip picking/depth pass for volume rendering
- not supported in shader anyway
- was printing 'no output' warning in Chrome console
2021-11-24 18:20:42 -08:00
Alexander Rose
c6b814b31b re-enable VAO with better workaround 2021-11-24 18:17:48 -08:00
Alexander Rose
98566fa389 improve error handling
- console.error if not re-thrown
- better messages for users
2021-11-24 18:15:47 -08:00
David Sehnal
4318c89bdb Merge pull request #288 from jpattle/allow-v3-sdf
Added the ability to handle v3000 sd files
2021-11-23 15:49:42 +01:00
Jason Pattle
b41a97ce6a Added a separate function to handle v2 counts and refactored the existing code that distinguishes v2 and v3 atom and bond counts 2021-11-23 10:30:09 +00:00
Jason Pattle
862c384dc0 Added the ability to handle v3000 sd files. Added a set of utility functions for parsing atoms and bonds from v3000 sd files. Updated the existing sdf parser to determine the version and run the v3000 sd file parser functions instead of the default v2000 ones. Added tests to verify parsing functionality for example v3000 ctab 2021-11-22 16:00:35 +00:00
Alexander Rose
26cc7e94c2 2.3.9 2021-11-20 16:58:28 -08:00
Alexander Rose
c6fe6ddcba switch off VAO support for now 2021-11-20 16:54:38 -08:00
Alexander Rose
154984e74d 2.3.8 2021-11-20 16:30:22 -08:00
Alexander Rose
72fcaf8321 changelog 2021-11-20 16:24:54 -08:00
Alexander Rose
0c14ca5888 workaround for VAO issue in Chrome 96 2021-11-20 16:14:30 -08:00
Alexander Rose
a85ede5058 fix unused vertex attribute handling 2021-11-20 16:13:18 -08:00
Alexander Rose
db49a16184 fix double canvas context creation 2021-11-20 12:52:47 -08:00
dsehnal
0704db2343 replace webpack-version-file-plugin 2021-11-20 13:45:28 +01:00
dsehnal
425dca4665 fix sass division 2021-11-20 12:55:46 +01:00
dsehnal
8d65ccabd2 update packages
- use sass instead of node sass
2021-11-20 12:49:26 +01:00
Alexander Rose
cbd417ca13 2.3.7 2021-11-15 19:31:07 -08:00
Alexander Rose
1e4d1e45f9 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-13 13:36:03 -08:00
Alexander Rose
1578211157 add helix profile option to cartoon repr 2021-11-13 13:16:11 -08:00
Alexander Rose
15932dc5df handle parent in Structure.remapModel 2021-11-13 09:23:57 -08:00
dsehnal
7db2205956 changelog 2021-11-09 21:18:55 +01:00
dsehnal
d87f0d236a ViewerOptions.collapseRightPanel 2021-11-09 21:17:01 +01:00
dsehnal
16daca6008 undo test code 2021-11-09 21:13:28 +01:00
dsehnal
a0d919c8db Viewer.loadTrajectory 2021-11-09 21:12:24 +01:00
David Sehnal
ffee2bf1c4 Merge pull request #285 from MadCatX/tweak-measurement-order-labels
Tweak measurement order labels
2021-11-09 10:28:13 +01:00
Michal Malý
de77f6ac59 Fix a visual glitch where the label border was initially rendered with
half of the intended size.
2021-11-09 09:59:25 +01:00
Michal Malý
c8c2ebcd65 Reduce Z-offset of measurement order labels to avoid too early clipping 2021-11-09 09:59:25 +01:00
Michal Malý
42796b984f Reduce border width of measurement order labels 2021-11-09 09:58:58 +01:00
David Sehnal
746557bf52 Merge pull request #284 from russellp17/master
Move jest types to devDependencies
2021-11-08 22:48:15 +01:00
Russell Parker
a5020a9e96 Move jest types to devDependencies 2021-11-08 15:58:14 -05:00
dsehnal
2ebb0a35fd 2.3.6 2021-11-08 18:57:06 +01:00
dsehnal
64aaa92d45 changelog 2021-11-08 18:54:52 +01:00
dsehnal
4baf391efe prefer webgl1 in safari 15.1
- WebGL2 is broken there for Mol* shaders
- It works again in Safari 15.4 tech preview
2021-11-08 18:50:16 +01:00
David Sehnal
5afdcff6a5 Merge pull request #273 from molstar/shader-tests
add basic unit tests for graphics shaders
2021-11-08 18:40:51 +01:00
dsehnal
339c397860 package lock 2021-11-08 18:30:52 +01:00
dsehnal
a58cbd31ef Merge branch 'master' of https://github.com/molstar/molstar into shader-tests 2021-11-08 18:30:30 +01:00
David Sehnal
d232b01cf9 Merge pull request #283 from MadCatX/improve-measurements-ux
Improve measurements user experience
2021-11-08 17:12:37 +01:00
Michal Malý
ec95270854 Use the entire element to trigger highlighting of loci from additionsHistory 2021-11-08 15:46:07 +01:00
Michal Malý
78cc0d960f Show the order of locis to be used for measurements in 3D view 2021-11-08 15:46:02 +01:00
Alexander Rose
b5ccdfdd53 multiple lights, per object materials
- multiple directional lights
- per-object materials (roughness & metalness)
- fixed reflectivity to 0.5
- update PhysicalMaterial from three.js to r134
2021-11-06 21:14:14 -07:00
Alexander Rose
d0c0d8e703 require standardDerivatives glsl extension 2021-11-06 20:57:22 -07:00
Alexander Rose
ebf64404be add loop unrolling glsl support 2021-11-06 15:02:14 -07:00
Alexander Rose
7f39cf0f37 add missing updateFocusRepr to atomicDetail preset
- fixes #280
2021-11-02 22:10:35 -07:00
Alexander Rose
e5dcc8e54f wip, lighting improvements
- set light direction
- set light color
- set ambient color
2021-10-31 14:59:19 -07:00
Alexander Rose
4592510a95 linting fix 2021-10-30 21:50:15 -07:00
Alexander Rose
46d5442dc5 Merge pull request #252 from corredD/forkdev
binary model loading support and latest mycoplasma model.
2021-10-30 17:11:27 -07:00
Alexander Rose
271cff4aba changelog 2021-10-30 17:09:35 -07:00
Alexander Rose
94fd5a97d6 Merge branch 'master' of https://github.com/molstar/molstar into pr/corredD/252 2021-10-30 16:53:35 -07:00
Alexander Rose
28678e2f80 Merge pull request #270 from molstar/measurements
Additional measurement controls
2021-10-30 16:52:32 -07:00
Alexander Rose
406307a432 add radiusScale param to orientation measurement 2021-10-30 16:48:47 -07:00
Alexander Rose
56345b5096 Merge branch 'master' of https://github.com/molstar/molstar into measurements 2021-10-30 16:31:12 -07:00
Alexander Rose
3fcc42ee0e fix, proper EmptyRepresentationProvider 2021-10-30 16:26:37 -07:00
Alexander Rose
b903677f8a gh action, add npm run build 2021-10-30 16:12:42 -07:00
Alexander Rose
ef4b632a07 update package-lock 2021-10-30 16:05:24 -07:00
Alexander Rose
e9d485ca85 gh action, use npm ci 2021-10-30 16:01:09 -07:00
Alexander Rose
a149fa5929 gh action 2021-10-30 15:53:58 -07:00
Alexander Rose
bb3dde585b gh action 2021-10-30 15:46:23 -07:00
Alexander Rose
cd6bbeaa86 gh action 2021-10-30 15:44:06 -07:00
Alexander Rose
e3d24dae4b gh action 2021-10-30 15:43:05 -07:00
Alexander Rose
687a814a62 gh action
- update eslint
- add jest
2021-10-30 15:02:25 -07:00
Alexander Rose
8f2e99dc51 improve shader tests
- made gl package optional
- skip test if gl package not available
2021-10-30 14:52:11 -07:00
Alexander Rose
568be030c3 Merge branch 'master' of https://github.com/molstar/molstar into shader-tests 2021-10-30 14:45:19 -07:00
Alexander Rose
97c3ab8b5a changelog 2021-10-30 14:44:27 -07:00
Alexander Rose
5db646d139 fix marker highlight color overriding select color 2021-10-30 14:43:57 -07:00
Alexander Rose
340f8f1af3 add additional aromatic bond visual params 2021-10-30 14:42:17 -07:00
dsehnal
4484a4452c 2.3.5 2021-10-19 18:22:50 +02:00
dsehnal
65b654a0a2 fix index pair bonds order assignment 2021-10-19 18:20:54 +02:00
dsehnal
a8e0c13b0e fix sequence viewer for PDB files with COMPND record and multichain entities 2021-10-17 13:21:57 +02:00
Alexander Rose
b371f8c11c Merge branch 'master' of https://github.com/molstar/molstar into shader-tests 2021-10-03 15:24:02 -07:00
Alexander Rose
895a13fc0d Merge branch 'master' into shader-tests 2021-10-03 14:36:23 -07:00
Alexander Rose
2f9ac711d1 add basic unit tests for graphics shaders
- compile using `gl` package
2021-10-03 10:52:58 -07:00
Alexander Rose
b06c134b61 add additional measurement controls
- orientation (box, axes, ellipsoid)
- plane (best fit)
2021-09-18 22:39:40 -07:00
Alexander Rose
3436d03468 add helpers to work with many locis
- StructureElement.Loci.getPrincipalAxesMany
- structureElementLociLabelMany
2021-09-18 22:13:22 -07:00
Alexander Rose
58df6f3b85 formating 2021-09-18 22:12:08 -07:00
Alexander Rose
ffaf008dce limit max display counts in sequence panel
- MaxSelectOptionsCount
- MaxSequenceWrappersCount
- workaround for cellpack models
2021-09-18 16:30:14 -07:00
ludovic autin
eb196a41b5 change variables names to avoid confusion with other types. Added the xrayshading for the compartment geometry 2021-09-14 11:25:15 -07:00
ludovic autin
35baaaf594 support for compartment PLY file. 2021-09-13 11:46:27 -07:00
Alexander Rose
0fc305aaea fix linting issues 2021-09-12 23:32:55 -07:00
Alexander Rose
bf67546a61 Merge branch 'master' of https://github.com/molstar/molstar into pr/corredD/252 2021-09-12 23:30:26 -07:00
Alexander Rose
80d54afdd0 Merge branch 'master' into forkdev 2021-09-12 20:16:00 -07:00
Alexander Rose
2a74dfcf46 factor out membrane sphere handling 2021-09-12 10:26:10 -07:00
Alexander Rose
b0f447adde fix cellpack results file asset handling 2021-09-12 10:17:37 -07:00
ludovic autin
b8628ccff1 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-09-01 09:55:13 -07:00
ludovic autin
4c30057edf I am trying to get the binary asset to get cached. But still can't save a working session. 2021-08-27 16:14:43 -07:00
ludovic autin
53a4826274 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2021-08-27 14:55:22 -07:00
ludovic autin
393fc99ed2 moved the model loading in model.ts and get the info from the recipe dictionary ( result file and lipids file ) if available. Fix the dates and some const / let variable defintion 2021-08-16 12:35:51 -07:00
ludovic autin
7c5dff1c8b Merge remote-tracking branch 'upstream/master' into forkdev 2021-08-16 11:55:09 -07:00
ludovic autin
ce3f13431d endianess test with IsNativeEndianLittle 2021-08-13 10:58:25 -07:00
ludovic autin
df54766ab2 lower-case icosahedron 2021-08-12 10:48:27 -07:00
ludovic autin
94233fbcd9 eslint error fix 2021-08-11 11:07:34 -07:00
ludovic autin
d044496eaa fix eslint errors 2021-08-11 11:04:08 -07:00
ludovic autin
ca825d720e binary model loading support, latest mycoplasma model. 2021-08-11 10:44:44 -07:00
161 changed files with 20198 additions and 4343 deletions

View File

@@ -1,18 +0,0 @@
on:
push:
pull_request:
jobs:
eslint:
name: eslint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: install node v14
uses: actions/setup-node@v1
with:
node-version: 14
- name: yarn install
run: yarn install
- name: eslint
uses: icrawl/action-eslint@v1

20
.github/workflows/node.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
- run: npm ci
- run: sudo apt-get install xvfb
- name: Lint
run: npm run lint
- name: Test
run: xvfb-run --auto-servernum npm run jest
- name: Build
run: npm run build

View File

@@ -1 +1 @@
tsconfig.commonjs.tsbuildinfo
lib/tsconfig.commonjs.tsbuildinfo

View File

@@ -6,6 +6,59 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.0.0-dev.0] - 2021-11-28
- Add multiple lights support (with color, intensity, and direction parameters)
- [Breaking] Add per-object material rendering properties
- Add substance theme with per-group material rendering properties
## [v2.4.1] - 2021-11-28
- Fix: allow atoms in aromatic rings to do hydrogen bonds
## [v2.4.0] - 2021-11-25
- Fix secondary-structure property handling
- StructureElement.Property was incorrectly resolving type & key
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
- Re-enable VAO with better workaround (bind null elements buffer before deleting)
- Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes)
- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces
## [v2.3.9] - 2021-11-20
- Workaround: switch off VAO support for now
## [v2.3.8] - 2021-11-20
- Fix double canvas context creation (in plugin context)
- Fix unused vertex attribute handling (track which are used, disable the rest)
- Workaround for VAO issue in Chrome 96 (can cause WebGL to crash on geometry updates)
## [v2.3.7] - 2021-11-15
- Added ``ViewerOptions.collapseRightPanel``
- Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
- Fix: handle parent in Structure.remapModel
- Add ``rounded`` and ``square`` helix profile options to Cartoon representation (in addition to the default ``elliptical``)
## [v2.3.6] - 2021-11-8
- Add additional measurement controls: orientation (box, axes, ellipsoid) & plane (best fit)
- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
- Fix marker highlight color overriding select color
- CellPack extension update
- add binary model support
- add compartment (including membrane) geometry support
- add latest mycoplasma model example
- Prefer WebGL1 in Safari 15.1.
## [v2.3.5] - 2021-10-19
- Fix sequence viewer for PDB files with COMPND record and multichain entities.
- Fix index pair bonds order assignment
## [v2.3.4] - 2021-10-12
- Fix pickScale not taken into account in line/point shader
@@ -148,29 +201,22 @@ Note that since we don't clearly distinguish between a public and private interf
- Fixed Measurements UI labels (#166)
## [v2.0.3] - 2021-04-09
### Added
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
### Changed
- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- ``Canvas3D.getRenderObjects``.
- Add ``Canvas3D.getRenderObjects``.
- [WIP] Animate state interpolating, including model trajectories
### Changed
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
### Fixed
- VolumeFromDensityServerCif transform label
- Fix VolumeFromDensityServerCif transform label
## [v2.0.1] - 2021-03-23
### Fixed
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
## [v2.0.0] - 2021-03-23
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

View File

@@ -122,9 +122,9 @@ and navigate to `build/viewer`
**Convert any CIF to BinaryCIF**
node lib/servers/model/preprocess -i file.cif -ob file.bcif
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
To see all available commands, use ``node lib/servers/model/preprocess -h``.
To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``.
Or

19186
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.3.4",
"version": "3.0.0-dev.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -89,51 +89,51 @@
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.1.0",
"@graphql-codegen/cli": "^2.2.0",
"@graphql-codegen/cli": "^2.3.0",
"@graphql-codegen/time": "^3.1.0",
"@graphql-codegen/typescript": "^2.2.2",
"@graphql-codegen/typescript": "^2.4.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
"@graphql-codegen/typescript-graphql-request": "^4.1.4",
"@graphql-codegen/typescript-operations": "^2.1.6",
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
"@graphql-codegen/typescript-operations": "^2.2.1",
"@types/cors": "^2.8.12",
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.32.0",
"@types/gl": "^4.1.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"benchmark": "^2.1.4",
"concurrently": "^6.3.0",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.3.0",
"eslint": "^7.32.0",
"css-loader": "^6.5.1",
"eslint": "^8.3.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.0",
"graphql": "^15.6.0",
"http-server": "^13.0.2",
"jest": "^27.2.4",
"mini-css-extract-plugin": "^2.3.0",
"node-sass": "^6.0.1",
"graphql": "^15.7.2",
"http-server": "^14.0.0",
"jest": "^27.3.1",
"mini-css-extract-plugin": "^2.4.5",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass-loader": "^12.1.0",
"simple-git": "^2.46.0",
"sass": "^1.43.5",
"sass-loader": "^12.3.0",
"simple-git": "^2.47.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.0",
"ts-jest": "^27.0.5",
"typescript": "^4.4.3",
"webpack": "^5.56.0",
"webpack-cli": "^4.8.0",
"webpack-version-file-plugin": "^0.4.0"
"style-loader": "^3.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.5.2",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.1",
"@types/compression": "1.7.2",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.2",
"@types/node": "^16.10.2",
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.27",
"@types/react-dom": "^17.0.9",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
"body-parser": "^1.19.0",
@@ -141,15 +141,18 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.6",
"immer": "^9.0.7",
"immutable": "^3.8.2",
"node-fetch": "^2.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rxjs": "^7.3.1",
"swagger-ui-dist": "^3.52.3",
"rxjs": "^7.4.0",
"swagger-ui-dist": "^4.1.1",
"tslib": "^2.3.1",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"
},
"optionalDependencies": {
"gl": "^4.9.2"
}
}

View File

@@ -26,7 +26,6 @@ function shinyStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
style: { name: 'plastic', params: {} },
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
@@ -40,7 +39,6 @@ function occlusionStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
style: { name: 'flat', params: {} }
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
@@ -94,8 +92,8 @@ export const StructurePreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, roughness: 0.2 }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -121,8 +119,8 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -149,8 +147,8 @@ const SurfacePreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, roughness: 0.2, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -177,8 +175,8 @@ const PocketPreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, roughness: 0.2, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
};
await update.commit({ revertOnError: true });
@@ -206,10 +204,10 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, 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, 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 }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, 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, roughness: 0.2, 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, roughness: 0.2 }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, roughness: 0.2, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
};
await update.commit({ revertOnError: true });

View File

@@ -36,7 +36,7 @@
emdbProvider: 'rcsb',
});
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
</script>

View File

@@ -9,25 +9,28 @@ import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Mp4Export } from '../../extensions/mp4-export';
import { GeometryExport } from '../../extensions/geo-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/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 { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectSelector } from '../../mol-state';
@@ -71,6 +74,7 @@ const DefaultViewerOptions = {
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
@@ -114,7 +118,7 @@ export class Viewer {
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
@@ -328,6 +332,56 @@ export class Viewer {
});
}
/**
* @example
* viewer.loadTrajectory({
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
* preset: 'all-models' // or 'default'
* });
*/
async loadTrajectory(params: LoadTrajectoryParams) {
const plugin = this.plugin;
let model: StateObjectSelector, coords: StateObjectSelector;
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
const data = params.model.kind === 'model-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
model = await plugin.builders.structure.createModel(trajectory);
} else {
const data = params.model.kind === 'topology-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const provider = plugin.dataFormats.get(params.model.format);
model = await provider!.parse(plugin, data);
}
{
const data = params.coordinates.kind === 'coordinates-data'
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
const provider = plugin.dataFormats.get(params.coordinates.format);
coords = await provider!.parse(plugin, data);
}
const trajectory = await plugin.build().toRoot()
.apply(TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coords.ref
}, { dependsOn: [model.ref, coords.ref] })
.commit();
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
return { model, coords, preset };
}
handleResize() {
this.plugin.layout.events.updated.next(void 0);
}
@@ -343,4 +397,16 @@ export interface VolumeIsovalueInfo {
color: Color,
alpha?: number,
volumeIndex?: number
}
export interface LoadTrajectoryParams {
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
modelLabel?: string,
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
coordinatesLabel?: string,
preset?: keyof PresetTrajectoryHierarchy
}

View File

@@ -84,6 +84,7 @@ export function getFieldType(type: string, description: string, values?: string[
case 'DateTime':
case 'Tag':
case 'Implied':
case 'Word':
return wrapContainer('str', ',', description, container);
case 'Real':
return wrapContainer('float', ',', description, container);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2021 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>
*/

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Ludovic Autin <autin@scripps.edu>
* @author Ludovic Autin <ludovic.autin@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/

View File

@@ -13,16 +13,27 @@ export interface CellPack {
export interface CellPacking {
name: string,
location: 'surface' | 'interior' | 'cytoplasme',
location: 'surface' | 'interior' | 'cytoplasme'
ingredients: Packing['ingredients']
compartment?: CellCompartment
}
//
export interface CellCompartment {
filename?: string
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
compartment_primitives?: CompartmentPrimitives
}
export interface Cell {
recipe: Recipe
options?: RecipeOptions
cytoplasme?: Packing
compartments?: { [key: string]: Compartment }
mapping_ids?: { [key: number]: [number, string] }
}
export interface RecipeOptions {
resultfile?: string
}
export interface Recipe {
@@ -35,8 +46,29 @@ export interface Recipe {
export interface Compartment {
surface?: Packing
interior?: Packing
geom?: unknown
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
mb?: CompartmentPrimitives
}
// Primitives discribing a compartment
export const enum CompartmentPrimitiveType {
MetaBall = 0,
Sphere = 1,
Cube = 2,
Cylinder = 3,
Cone = 4,
Plane = 5,
None = 6
}
export interface CompartmentPrimitives{
positions?: number[];
radii?: number[];
types?: CompartmentPrimitiveType[];
}
export interface Packing {
ingredients: { [key: string]: Ingredient }
}
@@ -64,11 +96,13 @@ export interface Ingredient {
[curveX: string]: unknown;
/** the orientation in the membrane */
principalAxis?: Vec3;
principalVector?: Vec3;
/** offset along membrane */
offset?: Vec3;
ingtype?: string;
color?: Vec3;
confidence?: number;
Type?: string;
}
export interface IngredientSource {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/

View File

@@ -2,13 +2,14 @@
* Copyright (c) 2019-2021 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>
*/
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
import { PluginContext } from '../../mol-plugin/context';
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Ingredient, IngredientSource, CellPacking } from './data';
import { Ingredient, CellPacking, CompartmentPrimitives } from './data';
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
@@ -17,7 +18,7 @@ import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
import { SymmetryOperator } from '../../mol-math/geometry';
import { Task, RuntimeContext } from '../../mol-task';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { getMatFromResamplePoints } from './curve';
import { compile } from '../../mol-script/runtime/query/compiler';
@@ -28,8 +29,9 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser';
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { readFromFile } from '../../mol-util/data-source';
import { objectForEach } from '../../mol-util/object';
import { readFromFile } from '../../mol-util/data-source';
import { ColorNames } from '../../mol-util/color/names';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/results/${fileName}`;
@@ -41,10 +43,14 @@ class TrajectoryCache {
get(id: string) { return this.map.get(id); }
}
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
baseUrl: string, trajCache: TrajectoryCache, location: string,
file?: Asset.File
) {
const assetManager = plugin.managers.asset;
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
if (location === 'surface') surface = true;
let trajectory = trajCache.get(id);
const assets: Asset.Wrapper[] = [];
if (!trajectory) {
@@ -72,6 +78,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
try {
const data = await getFromOPM(plugin, id, assetManager);
assets.push(data.asset);
data.pdb.id! = id.toUpperCase();
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
} catch (e) {
// fallback to getFromPdb
@@ -100,7 +107,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
return { model, assets };
}
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
let structure = Structure.ofModel(model);
const { assembly } = props;
@@ -108,11 +115,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
}
let query;
if (source.selection) {
const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
if (source.source.selection) {
const sel = source.source.selection;
// selection can have the model ID as well. remove it
const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
query = MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
})
]);
@@ -123,11 +131,11 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
})
]);
}
const compiled = compile<StructureSelection>(query);
const result = compiled(new QueryContext(structure));
structure = StructureSelection.unionStructure(result);
// change here if possible the label ?
// structure.label = source.name;
return structure;
}
@@ -141,9 +149,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) {
}
function getTransform(trans: Vec3, rot: Quat) {
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
Mat4.setTranslation(m, p);
return m;
}
@@ -168,7 +176,7 @@ function getCurveTransforms(ingredient: Ingredient) {
for (let i = 0; i < n; ++i) {
const cname = `curve${i}`;
if (!(cname in ingredient)) {
// console.warn(`Expected '${cname}' in ingredient`)
console.warn(`Expected '${cname}' in ingredient`);
continue;
}
const _points = ingredient[cname] as Vec3[];
@@ -179,7 +187,7 @@ function getCurveTransforms(ingredient: Ingredient) {
// test for resampling
const distance: number = Vec3.distance(_points[0], _points[1]);
if (distance >= segmentLength + 2.0) {
console.info(distance);
// console.info(distance);
resampling = true;
}
const points = new Float32Array(_points.length * 3);
@@ -190,8 +198,8 @@ function getCurveTransforms(ingredient: Ingredient) {
return instances;
}
function getAssembly(transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder();
function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder({ label: name });
const { units } = structure;
for (let i = 0, il = transforms.length; i < il; ++i) {
@@ -307,13 +315,13 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
});
const curveModel = await plugin.runTask(curveModelTask);
return getStructure(plugin, curveModel, ingredient.source);
// ingredient.source.selection = undefined;
return getStructure(plugin, curveModel, ingredient);
}
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
const { name, source, results, nbCurve } = ingredient;
if (source.pdb === 'None') return;
const file = ingredientFiles[source.pdb];
if (!file) {
// TODO can these be added to the library?
@@ -325,13 +333,13 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
}
// model id in case structure is NMR
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
if (!model) return;
let structure: Structure;
if (nbCurve) {
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
} else {
if ((!results || results.length === 0)) return;
let bu: string|undefined = source.bu ? source.bu : undefined;
if (bu) {
if (bu === 'AU') {
@@ -340,10 +348,11 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
bu = bu.slice(2);
}
}
structure = await getStructure(plugin, model, source, { assembly: bu });
structure = await getStructure(plugin, model, ingredient, { assembly: bu });
// transform with offset and pcp
let legacy: boolean = true;
if (ingredient.offset || ingredient.principalAxis) {
const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
if (pcp) {
legacy = false;
const structureMean = getStructureMean(structure);
Vec3.negate(structureMean, structureMean);
@@ -351,38 +360,44 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
Mat4.setTranslation(m1, structureMean);
structure = Structure.transform(structure, m1);
if (ingredient.offset) {
if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())) {
const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ??
if (location !== 'surface') {
Vec3.negate(o, o);
}
const m: Mat4 = Mat4.identity();
Mat4.setTranslation(m, ingredient.offset);
Mat4.setTranslation(m, o);
structure = Structure.transform(structure, m);
}
}
if (ingredient.principalAxis) {
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)) {
if (pcp) {
const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
if (!Vec3.exactEquals(p, Vec3.unitZ)) {
const q: Quat = Quat.identity();
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
Quat.rotationTo(q, p, Vec3.unitZ);
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
structure = Structure.transform(structure, m);
}
}
}
structure = getAssembly(getResultTransforms(results, legacy), structure);
structure = getAssembly(name, getResultTransforms(results, legacy), structure);
}
return { structure, assets };
}
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
return Task.create('Create Packing Structure', async ctx => {
const { ingredients, name } = packing;
const { ingredients, location, name } = packing;
const assets: Asset.Wrapper[] = [];
const trajCache = new TrajectoryCache();
const structures: Structure[] = [];
const colors: Color[] = [];
let skipColors: boolean = false;
for (const iName in ingredients) {
if (ctx.shouldUpdate) await ctx.update(iName);
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
if (ingredientStructure) {
structures.push(ingredientStructure.structure);
assets.push(...ingredientStructure.assets);
@@ -390,7 +405,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
if (c) {
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
} else {
skipColors = true;
colors.push(Color.fromNormalizedRgb(1, 0, 0));
}
}
}
@@ -414,21 +429,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = Structure.create(units);
const structure = Structure.create(units, { label: name + '.' + location });
for (let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
return { structure, assets, colors: skipColors ? undefined : colors };
return { structure, assets, colors: colors };
});
}
async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
for (let i = 0, il = packings.length; i < il; ++i) {
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
const points = json.data.points as number[];
const curve0: Vec3[] = [];
for (let j = 0, jl = points.length; j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j));
@@ -465,7 +479,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
}
}
}
let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
let geometry_membrane: boolean = false; // membrane can be a mesh geometry
let b = state.build().toRoot();
if (file) {
if (file.name.endsWith('.cif')) {
@@ -474,27 +489,82 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
}
} else {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
if (name.toLowerCase().endsWith('.bcif')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
} else if (name.toLowerCase().endsWith('.cif')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
} else if (name.toLowerCase().endsWith('.ply')) {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
geometry_membrane = true;
} else {
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
legacy_membrane = true;
}
}
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
representation: params.preset.representation,
const props = {
type: {
name: 'assembly' as const,
params: { id: '1' }
}
};
if (legacy_membrane) {
// old membrane
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
representation: params.preset.representation,
};
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
} else if (geometry_membrane) {
await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ShapeFromPly)
.apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
.commit({ revertOnError: true });
} else {
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
.commit({ revertOnError: true });
const membraneParams = {
representation: params.preset.representation,
};
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
}
}
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
const nSpheres = primitives.positions!.length / 3;
// console.log('ok mb ', nSpheres);
// TODO : take in account the type of the primitives.
for (let j = 0; j < nSpheres; j++) {
await state.build()
.toRoot()
.apply(CreateCompartmentSphere, {
center: Vec3.create(
primitives.positions![j * 3 + 0],
primitives.positions![j * 3 + 1],
primitives.positions![j * 3 + 2]
),
radius: primitives!.radii![j]
})
.commit();
}
}
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
const ingredientFiles = params.ingredients || [];
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
let resultsFile: Asset.File | null = params.results;
if (params.source.name === 'id') {
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
cellPackJson = state.build().toRoot()
@@ -506,29 +576,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
return;
}
let jsonFile: Asset.File;
let modelFile: Asset.File;
if (file.name.toLowerCase().endsWith('.zip')) {
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
if (data['model.json']) {
modelFile = Asset.File(new File([data['model.json']], 'model.json'));
} else {
throw new Error('model.json missing from zip file');
}
if (data['results.bin']) {
resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
}
objectForEach(data, (v, k) => {
if (k === 'model.json') return;
if (k === 'results.bin') return;
ingredientFiles.push(Asset.File(new File([v], k)));
});
} else {
jsonFile = file;
modelFile = file;
}
cellPackJson = state.build().toRoot()
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
.apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
}
const cellPackBuilder = cellPackJson
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack);
.apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
const { packings } = cellPackObject.obj!.data;
const { packings } = cellPackObject.obj!.data;
await handleHivRna(plugin, packings, params.baseUrl);
for (let i = 0, il = packings.length; i < il; ++i) {
@@ -544,8 +621,30 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
representation: params.preset.representation,
};
await CellpackPackingPreset.apply(packing, packingParams, plugin);
if (packings[i].location === 'surface' && params.membrane) {
await loadMembrane(plugin, packings[i].name, state, params);
if (packings[i].compartment) {
if (params.membrane === 'lipids') {
if (packings[i].compartment!.geom_type) {
if (packings[i].compartment!.geom_type === 'file') {
// TODO: load mesh files or vertex,faces data
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
} else if (packings[i].compartment!.compartment_primitives) {
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
}
} else {
// try loading membrane from repo as a bcif file or from the given list of files.
if (params.membrane === 'lipids') {
await loadMembrane(plugin, packings[i].name, state, params);
}
}
} else if (params.membrane === 'geometry') {
if (packings[i].compartment!.compartment_primitives) {
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
} else if (packings[i].compartment!.geom_type === 'file') {
if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
}
}
}
}
}
}
@@ -555,19 +654,19 @@ const LoadCellPackModelParams = {
'id': PD.Select('InfluenzaModel2.json', [
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
['HIV_immature_model.json', 'HIV immature'],
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
['Blood_HIV.json', 'Blood HIV'],
['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
['influenza_model1.json', 'Influenza envelope'],
['InfluenzaModel2.json', 'Influenza Complete'],
['InfluenzaModel2.json', 'Influenza complete'],
['ExosomeModel.json', 'Exosome Model'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
}, { options: [['id', 'Id'], ['file', 'File']] }),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
membrane: PD.Boolean(true),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
@@ -581,4 +680,4 @@ export const LoadCellPackModel = StateAction.build({
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
await loadPackings(ctx, taskCtx, state, params);
}));
}));

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { StateObjectRef } from '../../mol-state';
@@ -9,8 +10,6 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '..
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ColorNames } from '../../mol-util/color/names';
import { CellPackGenerateColorThemeProvider } from './color/generate';
import { CellPackInfoProvider } from './property';
import { CellPackProvidedColorThemeProvider } from './color/provided';
export const CellpackPackingPresetParams = {
traceOnly: PD.Boolean(true),
@@ -42,8 +41,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
Object.assign(reprProps, { sizeFactor: 2 });
}
const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
// default is generated
const color = CellPackGenerateColorThemeProvider.name;
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
const representations = {
@@ -92,4 +91,4 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
return { components, representations };
}
});
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
};
}
});
});

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2019-2021 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>
*/
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { Shape } from '../../mol-model/shape';
import { ColorNames } from '../../mol-util/color/names';
import { RuntimeContext } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
import { Sphere } from '../../mol-geo/primitive/sphere';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
interface MembraneSphereData {
radius: number
center: Vec3
}
const MembraneSphereParams = {
...Mesh.Params,
cellColor: PD.Color(ColorNames.orange),
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
center: PD.Vec3(Vec3.create(0, 0, 0)),
quality: { ...Mesh.Params.quality, isEssential: false },
};
type MeshParams = typeof MembraneSphereParams
const MembraneSphereVisuals = {
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
};
export const MBParams = {
...MembraneSphereParams
};
export type MBParams = typeof MBParams
export type UnitcellProps = PD.Values<MBParams>
function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
const state = MeshBuilder.createState(256, 128, mesh);
const radius = props.radius;
const asphere = Sphere(3);
const trans: Mat4 = Mat4.identity();
Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
state.currentGroup = 1;
MeshBuilder.addPrimitive(state, trans, asphere);
const m = MeshBuilder.getMesh(state);
return m;
}
function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
const geo = getMBMesh(data, props, shape && shape.geometry);
const label = 'mb';
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
}
export type MBRepresentation = Representation<MembraneSphereData, MBParams>
export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
}

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
@@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context';
import { CellPackInfoProvider } from './property';
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { Vec3, Quat } from '../../mol-math/linear-algebra';
import { StateTransformer } from '../../mol-state';
import { MBRepresentation, MBParams } from './representation';
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
import { getFloatValue } from './util';
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
export { ParseCellPack };
@@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({
name: 'parse-cellpack',
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
from: PSO.Format.Json,
to: CellPack
to: CellPack,
params: a => {
return {
resultsFile: PD.File({ accept: '.bin' }),
baseUrl: PD.Text(DefaultCellPackBaseUrl)
};
}
})({
apply({ a }) {
apply({ a, params, cache }, plugin: PluginContext) {
return Task.create('Parse CellPack', async ctx => {
const cell = a.data as Cell;
let counter_id = 0;
let fiber_counter_id = 0;
let comp_counter = 0;
const packings: CellPacking[] = [];
const { compartments, cytoplasme } = cell;
if (!cell.mapping_ids) cell.mapping_ids = {};
if (cytoplasme) {
packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
for (const iName in cytoplasme.ingredients) {
if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
if (compartments) {
for (const name in compartments) {
const { surface, interior } = compartments[name];
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
let filename = '';
if (compartments[name].geom_type === 'file') {
filename = (compartments[name].geom) ? compartments[name].geom as string : '';
}
const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
if (surface) {
packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
for (const iName in surface.ingredients) {
if (surface.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
if (interior) {
if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
for (const iName in interior.ingredients) {
if (interior.ingredients[iName].ingtype === 'fiber') {
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
fiber_counter_id++;
} else {
cell.mapping_ids[counter_id] = [comp_counter, iName];
if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
counter_id++;
}
}
comp_counter++;
}
}
}
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
const { options } = cell;
let resultsAsset: Asset.Wrapper<'binary'> | undefined;
if (params.resultsFile) {
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
} else if (options?.resultfile) {
const url = `${params.baseUrl}/results/${options.resultfile}`;
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
}
if (resultsAsset) {
(cache as any).asset = resultsAsset;
const results = resultsAsset.data;
// flip the byte order if needed
const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
const numbers = new DataView(buffer);
const ninst = getFloatValue(numbers, 0);
const npoints = getFloatValue(numbers, 4);
const ncurve = getFloatValue(numbers, 8);
let offset = 12;
if (ninst !== 0) {
const pos = new Float32Array(buffer, offset, ninst * 4);
offset += ninst * 4 * 4;
const quat = new Float32Array(buffer, offset, ninst * 4);
offset += ninst * 4 * 4;
for (let i = 0; i < ninst; i++) {
const x: number = pos[i * 4 + 0];
const y: number = pos[i * 4 + 1];
const z: number = pos[i * 4 + 2];
const ingr_id = pos[i * 4 + 3] as number;
const pid = cell.mapping_ids![ingr_id];
if (!packings[pid[0]].ingredients[pid[1]].results) {
packings[pid[0]].ingredients[pid[1]].results = [];
}
packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
}
}
if (npoints !== 0) {
const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
offset += npoints * 4 * 4;
offset += npoints * 4 * 4;
const ctr_info = new Float32Array(buffer, offset, npoints * 4);
offset += npoints * 4 * 4;
const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
offset += ncurve * 4 * 4;
let counter = 0;
let ctr_points: Vec3[] = [];
let prev_ctype = 0;
let prev_cid = 0;
for (let i = 0; i < npoints; i++) {
const x: number = -ctr_pos[i * 4 + 0];
const y: number = ctr_pos[i * 4 + 1];
const z: number = ctr_pos[i * 4 + 2];
const cid: number = ctr_info[i * 4 + 0]; // curve id
const ctype: number = curve_ids[cid * 4 + 0]; // curve type
// cid 148 165 -1 0
// console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
if (prev_ctype !== ctype) {
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
ctr_points = [];
counter = 0;
} else if (prev_cid !== cid) {
ctr_points = [];
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
counter += 1;
}
ctr_points.push(Vec3.create(x, y, z));
prev_ctype = ctype;
prev_cid = cid;
}
// do the last one
const pid = cell.mapping_ids![-prev_ctype - 1];
const cname = `curve${counter}`;
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
}
}
return new CellPack({ cell, packings });
});
}
},
dispose({ cache }) {
((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
},
});
export { StructureFromCellpack };
@@ -77,9 +229,8 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
});
(cache as any).assets = assets;
return new PSO.Molecule.Structure(structure, { label: packing.name });
return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
});
},
dispose({ b, cache }) {
@@ -125,7 +276,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
structures.push(s);
}
const builder = Structure.Builder();
const builder = Structure.Builder({ label: 'Membrane' });
let offsetInvariantId = 0;
for (const s of structures) {
let maxInvariantId = 0;
@@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
b?.data.customPropertyDescriptors.dispose();
}
});
const CreateTransformer = StateTransformer.builderFactory('cellPACK');
export const CreateCompartmentSphere = CreateTransformer({
name: 'create-compartment-sphere',
display: 'CompartmentSphere',
from: PSO.Root, // or whatever data source
to: PSO.Shape.Representation3D,
params: {
center: PD.Vec3(Vec3()),
radius: PD.Numeric(1),
label: PD.Text(`Compartment Sphere`)
}
})({
canAutoUpdate({ oldParams, newParams }) {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Compartment Sphere', async ctx => {
const data = params;
const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
});
}
});

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
*/
import { CIF } from '../../mol-io/reader/cif';
@@ -37,7 +38,7 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, asset
}
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
return { mmcif: cif.blocks[0], asset };
}
@@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) {
}
const { elementCount } = structure;
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
}
export function getFloatValue(value: DataView, offset: number) {
// if the last byte is a negative value (MSB is 1), the final
// float should be too
const negative = value.getInt8(offset + 2) >>> 31;
// this is how the bytes are arranged in the byte array/DataView
// buffer
const [b0, b1, b2, exponent] = [
// get first three bytes as unsigned since we only care
// about the last 8 bits of 32-bit js number returned by
// getUint8().
// Should be the same as: getInt8(offset) & -1 >>> 24
value.getUint8(offset),
value.getUint8(offset + 1),
value.getUint8(offset + 2),
// get the last byte, which is the exponent, as a signed int
// since it's already correct
value.getInt8(offset + 3)
];
let mantissa = b0 | (b1 << 8) | (b2 << 16);
if (negative) {
// need to set the most significant 8 bits to 1's since a js
// number is 32 bits but our mantissa is only 24.
mantissa |= 255 << 24;
}
return mantissa * Math.pow(10, exponent);
}

View File

@@ -4,7 +4,6 @@
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { getStyle } from '../../mol-gl/renderer';
import { Box3D } from '../../mol-math/geometry';
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
@@ -46,13 +45,12 @@ export class GeometryControls extends PluginComponent {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
switch (this.behaviors.params.value.format) {
case 'glb':
renderObjectExporter = new GlbExporter(style, boundingBox);
renderObjectExporter = new GlbExporter(boundingBox);
break;
case 'obj':
renderObjectExporter = new ObjExporter(filename, boundingBox);
@@ -61,7 +59,7 @@ export class GeometryControls extends PluginComponent {
renderObjectExporter = new StlExporter(boundingBox);
break;
case 'usdz':
renderObjectExporter = new UsdzExporter(style, boundingBox, boundingSphere.radius);
renderObjectExporter = new UsdzExporter(boundingBox, boundingSphere.radius);
break;
default: throw new Error('Unsupported format.');
}
@@ -77,7 +75,7 @@ export class GeometryControls extends PluginComponent {
filename: filename + '.' + renderObjectExporter.fileExtension
};
} catch (e) {
this.plugin.log.error('' + e);
this.plugin.log.error('Error during geometry export');
throw e;
}
});

View File

@@ -2,10 +2,9 @@
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { BaseValues } from '../../mol-gl/renderable/schema';
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
import { Box3D } from '../../mol-math/geometry';
@@ -15,7 +14,7 @@ import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color/color';
import { fillSerial } from '../../mol-util/array';
import { NumberArray } from '../../mol-util/type-helpers';
import { MeshExporter, AddMeshInput } from './mesh-exporter';
import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
@@ -30,6 +29,12 @@ const FLOAT = 5126;
const ARRAY_BUFFER = 34962;
const ELEMENT_ARRAY_BUFFER = 34963;
const GLTF_MAGIC_BYTE = 0x46546C67;
const JSON_CHUNK_TYPE = 0x4E4F534A;
const BIN_CHUNK_TYPE = 0x004E4942;
const JSON_PAD_CHAR = 0x20;
const BIN_PAD_CHAR = 0x00;
export type GlbData = {
glb: Uint8Array
}
@@ -38,6 +43,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
readonly fileExtension = 'glb';
private nodes: Record<string, any>[] = [];
private meshes: Record<string, any>[] = [];
private materials: Record<string, any>[] = [];
private materialMap = new Map<string, number>();
private accessors: Record<string, any>[] = [];
private bufferViews: Record<string, any>[] = [];
private binaryBuffer: ArrayBuffer[] = [];
@@ -126,23 +133,17 @@ export class GlbExporter extends MeshExporter<GlbData> {
};
}
private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined) {
const groupCount = values.uGroupCount.ref.value;
private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) {
const { values, vertexCount } = geoData;
const uAlpha = values.uAlpha.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value;
const colorArray = new Uint8Array(vertexCount * 4);
for (let i = 0; i < vertexCount; ++i) {
let color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i);
let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint);
let alpha = uAlpha;
if (dTransparency) {
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
alpha *= 1 - transparency;
}
const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = uAlpha * (1 - transparency);
color = Color.sRGBToLinear(color);
Color.toArray(color, colorArray, i * 4);
@@ -157,20 +158,53 @@ export class GlbExporter extends MeshExporter<GlbData> {
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
}
private addMaterial(metalness: number, roughness: number) {
const hash = `${metalness}|${roughness}`;
if (!this.materialMap.has(hash)) {
this.materialMap.set(hash, this.materials.length);
this.materials.push({
pbrMetallicRoughness: {
baseColorFactor: [1, 1, 1, 1],
metallicFactor: metalness,
roughnessFactor: roughness
}
});
}
return this.materialMap.get(hash)!;
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
const t = Mat4();
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const dTransparency = values.dTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
const material = this.addMaterial(metalness, roughness);
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint: Uint8Array | undefined;
if (overpaintType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency: Uint8Array | undefined;
if (transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
}
// instancing
@@ -201,7 +235,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
// create a color buffer if needed
if (instanceIndex === 0 || !sameColorBuffer) {
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors);
colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
}
// glTF mesh
@@ -214,7 +248,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
COLOR_0: colorAccessorIndex!
},
indices: indexAccessorIndex,
material: 0
material
}]
});
}
@@ -248,13 +282,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
}],
bufferViews: this.bufferViews,
accessors: this.accessors,
materials: [{
pbrMetallicRoughness: {
baseColorFactor: [1, 1, 1, 1],
metallicFactor: this.style.metalness,
roughnessFactor: this.style.roughness
}
}]
materials: this.materials
};
const createChunk = (chunkType: number, data: ArrayBuffer[], byteLength: number, padChar: number): [ArrayBuffer[], number] => {
@@ -279,13 +307,13 @@ export class GlbExporter extends MeshExporter<GlbData> {
const jsonBuffer = new Uint8Array(jsonString.length);
asciiWrite(jsonBuffer, jsonString);
const [jsonChunk, jsonChunkLength] = createChunk(0x4E4F534A, [jsonBuffer.buffer], jsonBuffer.length, 0x20);
const [binaryChunk, binaryChunkLength] = createChunk(0x004E4942, this.binaryBuffer, binaryBufferLength, 0x00);
const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR);
const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR);
const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
const header = new ArrayBuffer(12);
const headerDataView = new DataView(header);
headerDataView.setUint32(0, 0x46546C67, true); // magic number "glTF"
headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF"
headerDataView.setUint32(4, 2, true); // version
headerDataView.setUint32(8, glbBufferLength, true); // length
const glbBuffer = [header, ...jsonChunk, ...binaryChunk];
@@ -303,7 +331,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
}
constructor(private style: Style, boundingBox: Box3D) {
constructor(boundingBox: Box3D) {
super();
const tmpV = Vec3();
Vec3.add(tmpV, boundingBox.min, boundingBox.max);

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { sort, arraySwap } from '../../mol-data/util';
@@ -26,6 +27,7 @@ import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
const GeoExportName = 'geo-export';
@@ -48,6 +50,14 @@ export interface AddMeshInput {
ctx: RuntimeContext
}
export type MeshGeoData = {
values: BaseValues,
groups: Float32Array | Uint8Array,
vertexCount: number,
instanceIndex: number,
isGeoTexture: boolean
}
export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
abstract readonly fileExtension: string;
@@ -90,26 +100,43 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
return decodeFloatRGB(r, g, b);
}
protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
const { values, vertexCount, vertices, colorType, stride } = input;
const colorGridTransform = values.uColorGridTransform.ref.value;
const colorGridDim = values.uColorGridDim.ref.value;
const colorTexDim = values.uColorTexDim.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[GeoExportName];
const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
return interpolated.array;
}
const [width, height] = colorTexDim;
const colorGrid = new Uint8Array(width * height * 4);
protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
const { values, vertexCount, vertices, colorType, stride } = input;
const overpaintGridTransform = values.uOverpaintGridTransform.ref.value;
const overpaintGridDim = values.uOverpaintGridDim.ref.value;
const overpaintTexDim = values.uOverpaintTexDim.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
framebuffer.bind();
values.tColorGrid.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, colorGrid);
const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array;
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 });
return interpolated.array;
}
protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
const { values, vertexCount, vertices, colorType, stride } = input;
const transparencyGridTransform = values.uTransparencyGridTransform.ref.value;
const transparencyGridDim = values.uTransparencyGridDim.ref.value;
const transparencyTexDim = values.uTransparencyTexDim.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array;
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 });
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
return interpolated.array;
}
@@ -194,11 +221,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
}
}
protected static getColor(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined, vertexIndex: number): Color {
protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const uColor = values.uColor.ref.value;
const tColor = values.tColor.ref.value.array;
const overpaintType = values.dOverpaintType.ref.value;
const dOverpaint = values.dOverpaint.ref.value;
const tOverpaint = values.tOverpaint.ref.value.array;
@@ -236,15 +265,70 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
}
if (dOverpaint) {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
const overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4);
const overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255;
let overpaintColor: Color;
let overpaintAlpha: number;
switch (overpaintType) {
case 'groupInstance': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
const idx = (instanceIndex * groupCount + group) * 4;
overpaintColor = Color.fromArray(tOverpaint, idx);
overpaintAlpha = tOverpaint[idx + 3] / 255;
break;
}
case 'vertexInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
overpaintColor = Color.fromArray(tOverpaint, idx);
overpaintAlpha = tOverpaint[idx + 3] / 255;
break;
}
case 'volumeInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
overpaintColor = Color.fromArray(interpolatedOverpaint!, idx);
overpaintAlpha = interpolatedOverpaint![idx + 3] / 255;
break;
}
default: throw new Error('Unsupported overpaint type.');
}
// interpolate twice to avoid darkening due to empty overpaint
overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha);
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
}
return color;
}
protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
const groupCount = values.uGroupCount.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value.array;
const transparencyType = values.dTransparencyType.ref.value;
let transparency: number = 0;
if (dTransparency) {
switch (transparencyType) {
case 'groupInstance': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
const idx = (instanceIndex * groupCount + group);
transparency = tTransparency[idx] / 255;
break;
}
case 'vertexInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex);
transparency = tTransparency[idx] / 255;
break;
}
case 'volumeInstance': {
const idx = (instanceIndex * vertexCount + vertexIndex);
transparency = interpolatedTransparency![idx] / 255;
break;
}
default: throw new Error('Unsupported transparency type.');
}
}
return transparency;
}
protected abstract addMeshWithColors(input: AddMeshInput): void;
private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { asciiWrite } from '../../mol-io/common/ascii';
@@ -47,7 +48,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
StringBuilder.newline(this.obj);
if (!this.materialSet.has(material)) {
this.materialSet.add(material);
const [r, g, b] = Color.toRgbNormalized(color);
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
const mtl = this.mtl;
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
@@ -77,18 +78,27 @@ export class ObjExporter extends MeshExporter<ObjData> {
const tmpV = Vec3();
const stride = isGeoTexture ? 4 : 3;
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const uAlpha = values.uAlpha.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint: Uint8Array | undefined;
if (overpaintType === 'volumeInstance') {
interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency: Uint8Array | undefined;
if (transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
}
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -126,17 +136,23 @@ export class ObjExporter extends MeshExporter<ObjData> {
StringBuilder.newline(obj);
}
// face
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
// color
const quantizedColors = new Uint8Array(drawCount * 3);
for (let i = 0; i < drawCount; i += 3) {
const v = isGeoTexture ? i : indices![i];
const color = ObjExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
let alpha = uAlpha;
if (dTransparency) {
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
alpha *= 1 - transparency;
}
// face
for (let i = 0; i < drawCount; i += 3) {
const color = Color.fromArray(quantizedColors, i);
const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
this.updateMaterial(color, alpha);

View File

@@ -79,10 +79,10 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
this.setState({ busy: false });
download(data.blob, data.filename);
} catch {
} catch (e) {
console.error(e);
} finally {
this.setState({ busy: false });
}
}
@@ -91,7 +91,6 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
this.setState({ busy: false });
const a = document.createElement('a');
a.rel = 'ar';
a.href = URL.createObjectURL(data.blob);
@@ -100,7 +99,9 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
a.appendChild(document.createElement('img'));
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
} catch {
} catch (e) {
console.error(e);
} finally {
this.setState({ busy: false });
}
}

View File

@@ -2,9 +2,9 @@
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
@@ -31,18 +31,17 @@ export class UsdzExporter extends MeshExporter<UsdzData> {
readonly fileExtension = 'usdz';
private meshes: string[] = [];
private materials: string[] = [];
private materialSet = new Set<number>();
private materialMap = new Map<string, number>();
private centerTransform: Mat4;
private static getMaterialKey(color: Color, alpha: number) {
return color * 256 + Math.round(alpha * 255);
}
private addMaterial(color: Color, alpha: number, metalness: number, roughness: number): number {
const hash = `${color}|${alpha}|${metalness}|${roughness}`;
if (this.materialMap.has(hash)) return this.materialMap.get(hash)!;
private addMaterial(color: Color, alpha: number) {
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
if (this.materialSet.has(materialKey)) return;
this.materialSet.add(materialKey);
const [r, g, b] = Color.toRgbNormalized(color);
const materialKey = this.materialMap.size;
this.materialMap.set(hash, materialKey);
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
this.materials.push(`
def Material "material${materialKey}"
{
@@ -52,12 +51,13 @@ def Material "material${materialKey}"
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (${r},${g},${b})
float inputs:opacity = ${alpha}
float inputs:metallic = ${this.style.metalness}
float inputs:roughness = ${this.style.roughness}
float inputs:metallic = ${metalness}
float inputs:roughness = ${roughness}
token outputs:surface
}
}
`);
return materialKey;
}
protected async addMeshWithColors(input: AddMeshInput) {
@@ -68,18 +68,30 @@ def Material "material${materialKey}"
const tmpV = Vec3();
const stride = isGeoTexture ? 4 : 3;
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const uAlpha = values.uAlpha.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint: Uint8Array | undefined;
if (overpaintType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency: Uint8Array | undefined;
if (transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
}
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -121,6 +133,8 @@ def Material "material${materialKey}"
StringBuilder.writeSafe(normalBuilder, ')');
}
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
// face
for (let i = 0; i < drawCount; ++i) {
const v = isGeoTexture ? i : indices![i];
@@ -129,21 +143,23 @@ def Material "material${materialKey}"
}
// color
const faceIndicesByMaterial = new Map<number, number[]>();
const quantizedColors = new Uint8Array(drawCount * 3);
for (let i = 0; i < drawCount; i += 3) {
const v = isGeoTexture ? i : indices![i];
const color = UsdzExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
UsdzExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
let alpha = uAlpha;
if (dTransparency) {
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
alpha *= 1 - transparency;
}
// material
const faceIndicesByMaterial = new Map<number, number[]>();
for (let i = 0; i < drawCount; i += 3) {
const color = Color.fromArray(quantizedColors, i);
this.addMaterial(color, alpha);
const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
const materialKey = this.addMaterial(color, alpha, metalness, roughness);
let faceIndices = faceIndicesByMaterial.get(materialKey);
if (faceIndices === undefined) {
faceIndices = [];
@@ -215,7 +231,7 @@ def Mesh "mesh${this.meshes.length}"
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
}
constructor(private style: Style, boundingBox: Box3D, radius: number) {
constructor(boundingBox: Box3D, radius: number) {
super();
const t = Mat4();
// scale the model so that it fits within 1 meter

View File

@@ -73,7 +73,7 @@ export class Mp4Controls extends PluginComponent {
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
} catch (e) {
this.plugin.log.error('' + e);
this.plugin.log.error('Error during animation export');
throw e;
}
});

View File

@@ -115,7 +115,8 @@ export class Mp4EncoderUI extends CollapsableControls<{}, State> {
this.setState({ busy: true });
const data = await this.controls.render();
this.setState({ busy: false, data });
} catch {
} catch (e) {
console.error(e);
this.setState({ busy: false });
}
}

View File

@@ -1,9 +1,10 @@
/* eslint-disable */
export type Maybe<T> = T | null;
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 2021-09-12T13:31:40-07:00
// Generated on 2021-11-25T14:34:23-08:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -6769,7 +6770,7 @@ export type Query = {
/** Query root */
export type QueryAssembliesArgs = {
assembly_ids: ReadonlyArray<Maybe<Scalars['String']>>;
assembly_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6802,7 +6803,7 @@ export type QueryBranched_Entity_InstanceArgs = {
/** Query root */
export type QueryBranched_Entity_InstancesArgs = {
instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6814,7 +6815,7 @@ export type QueryChem_CompArgs = {
/** Query root */
export type QueryChem_CompsArgs = {
comp_ids: ReadonlyArray<Maybe<Scalars['String']>>;
comp_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6852,7 +6853,7 @@ export type QueryNonpolymer_Entity_InstanceArgs = {
/** Query root */
export type QueryNonpolymer_Entity_InstancesArgs = {
instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -6878,7 +6879,7 @@ export type QueryPolymer_Entity_InstanceArgs = {
/** Query root */
export type QueryPolymer_Entity_InstancesArgs = {
instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
};
@@ -13144,4 +13145,4 @@ export type AssemblySymmetryQueryVariables = Exact<{
}>;
export type AssemblySymmetryQuery = { readonly assembly?: Maybe<{ readonly rcsb_struct_symmetry?: Maybe<ReadonlyArray<Maybe<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<Maybe<string>>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<Maybe<{ readonly avg_rmsd?: Maybe<number>, readonly members: ReadonlyArray<Maybe<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: Maybe<ReadonlyArray<Maybe<string>>> }>> }>>, readonly rotation_axes?: Maybe<ReadonlyArray<Maybe<{ readonly order?: Maybe<number>, readonly start: ReadonlyArray<Maybe<number>>, readonly end: ReadonlyArray<Maybe<number>> }>>> }>>> }> };
export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null | undefined>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null | undefined, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null | undefined> | null | undefined } | null | undefined> } | null | undefined>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null | undefined, readonly start: ReadonlyArray<number | null | undefined>, readonly end: ReadonlyArray<number | null | undefined> } | null | undefined> | null | undefined } | null | undefined> | null | undefined } | null | undefined };

View File

@@ -22,11 +22,11 @@ import { Helper } from '../helper/helper';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { depthMerge_frag } from '../../mol-gl/shader/depth-merge.frag';
import { copy_frag } from '../../mol-gl/shader/copy.frag';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
import { MarkingPass, MarkingProps } from './marking';
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
const DepthMergeSchema = {
...QuadSchema,
@@ -53,27 +53,6 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
return createComputeRenderable(renderItem, values);
}
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
function getCopyRenderable(ctx: WebGLContext, colorTexture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export class DrawPass {
private readonly drawTarget: RenderTarget
@@ -128,8 +107,8 @@ export class DrawPass {
this.postprocessing = new PostprocessingPass(webgl, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.copyFboTarget = getCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = getCopyRenderable(webgl, this.postprocessing.target.texture);
this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = createCopyRenderable(webgl, this.postprocessing.target.texture);
}
reset() {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 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>
*/
@@ -15,6 +15,8 @@ import { ColorNames } from '../../mol-util/color/names';
import { NullLocation } from '../../mol-model/location';
import { UniformColorTheme } from '../../mol-theme/color/uniform';
import { UniformSizeTheme } from '../../mol-theme/size/uniform';
import { smoothstep } from '../../mol-math/interpolate';
import { Material } from '../../mol-util/material';
export const VisualQualityInfo = {
'custom': {},
@@ -33,19 +35,55 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames);
//
export namespace BaseGeometry {
export const Params = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
};
export type Params = typeof Params
export const ColorSmoothingParams = {
smoothColors: PD.MappedStatic('auto', {
auto: PD.Group({}),
on: PD.Group({
resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
}),
off: PD.Group({})
}),
};
export type ColorSmoothingParams = typeof ColorSmoothingParams
export function hasColorSmoothingProp(props: PD.Values<any>): props is PD.Values<ColorSmoothingParams> {
return !!props.smoothColors;
}
export function getColorSmoothingProps(smoothColors: PD.Values<ColorSmoothingParams>['smoothColors'], preferSmoothing?: boolean, resolution?: number) {
if ((smoothColors.name === 'on' || (smoothColors.name === 'auto' && preferSmoothing)) && resolution && resolution < 3) {
let stride = 3;
if (smoothColors.name === 'on') {
resolution *= smoothColors.params.resolutionFactor;
stride = smoothColors.params.sampleStride;
} else {
// https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
resolution *= 2 - smoothstep(0, 1.1, resolution);
resolution = Math.max(0.5, resolution);
if (resolution > 1.2) stride = 2;
}
return { resolution, stride };
};
}
//
export namespace BaseGeometry {
export const MaterialCategory: PD.Info = { category: 'Material' };
export const ShadingCategory: PD.Info = { category: 'Shading' };
export const CustomQualityParamInfo: PD.Info = {
category: 'Custom Quality',
hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
};
export const Params = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
material: Material.getParam(),
};
export type Params = typeof Params
export type Counts = { drawCount: number, vertexCount: number, groupCount: number, instanceCount: number }
export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
@@ -59,17 +97,24 @@ export namespace BaseGeometry {
}
export function createValues(props: PD.Values<Params>, counts: Counts) {
const { metalness, roughness } = Material.toObjectNormalized(props.material);
return {
alpha: ValueCell.create(props.alpha),
uAlpha: ValueCell.create(props.alpha),
uVertexCount: ValueCell.create(counts.vertexCount),
uGroupCount: ValueCell.create(counts.groupCount),
drawCount: ValueCell.create(counts.drawCount),
uMetalness: ValueCell.create(metalness),
uRoughness: ValueCell.create(roughness),
dLightCount: ValueCell.create(1),
};
}
export function updateValues(values: BaseValues, props: PD.Values<Params>) {
const { metalness, roughness } = Material.toObjectNormalized(props.material);
ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
ValueCell.updateIfChanged(values.uMetalness, metalness);
ValueCell.updateIfChanged(values.uRoughness, roughness);
}
export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {

View File

@@ -25,6 +25,7 @@ import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { CylindersValues } from '../../../mol-gl/renderable/cylinders';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
export interface Cylinders {
readonly kind: 'cylinders',
@@ -200,6 +201,7 @@ export namespace Cylinders {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: cylinders.cylinderCount * 4 * 3, vertexCount: cylinders.cylinderCount * 6, groupCount, instanceCount };
@@ -224,6 +226,7 @@ export namespace Cylinders {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -28,6 +28,7 @@ import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './
import { createEmptyClipping } from '../clipping-data';
import { Grid, Volume } from '../../../mol-model/volume';
import { ColorNames } from '../../../mol-util/color/names';
import { createEmptySubstance } from '../substance-data';
const VolumeBox = Box();
@@ -246,6 +247,7 @@ export namespace DirectVolume {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const [x, y, z] = gridDimension.ref.value;
@@ -270,6 +272,7 @@ export namespace DirectVolume {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,
...BaseGeometry.createValues(props, counts),
@@ -319,8 +322,7 @@ export namespace DirectVolume {
}
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
ValueCell.updateIfChanged(values.alpha, props.alpha);
ValueCell.updateIfChanged(values.uAlpha, props.alpha);
BaseGeometry.updateValues(values, props);
ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);

View File

@@ -26,6 +26,7 @@ import { fillSerial } from '../../../mol-util/array';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { QuadPositions } from '../../../mol-gl/compute/util';
import { createEmptySubstance } from '../substance-data';
const QuadIndices = new Uint32Array([
0, 1, 2,
@@ -145,6 +146,7 @@ namespace Image {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: QuadIndices.length, vertexCount: QuadPositions.length / 3, groupCount, instanceCount };
@@ -157,6 +159,7 @@ namespace Image {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,
...BaseGeometry.createValues(props, counts),

View File

@@ -26,6 +26,7 @@ import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
/** Wide line */
export interface Lines {
@@ -210,6 +211,7 @@ export namespace Lines {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: lines.lineCount * 2 * 3, vertexCount: lines.lineCount * 4, groupCount, instanceCount };
@@ -231,6 +233,7 @@ export namespace Lines {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -40,7 +40,7 @@ const v3set = Vec3.set;
const caAdd3 = ChunkedArray.add3;
const caAdd = ChunkedArray.add;
function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number) {
function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number, flip: boolean) {
const { vertices, normals, indices } = state;
const vertexCount = vertices.elementCount;
@@ -74,11 +74,19 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi
v3copy(verticalVector, verticalLeftVector);
}
for (let i = 0; i < 4; ++i) {
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
if (flip) {
for (let i = 0; i < 4; ++i) {
caAdd3(normals, -normalVector[0], -normalVector[1], -normalVector[2]);
}
caAdd3(indices, vertexCount, vertexCount + 1, vertexCount + 2);
caAdd3(indices, vertexCount + 2, vertexCount + 3, vertexCount);
} else {
for (let i = 0; i < 4; ++i) {
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
}
caAdd3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
caAdd3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
}
caAdd3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
caAdd3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
}
/** set arrowHeight = 0 for no arrow */
@@ -193,19 +201,18 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
const width = widthValues[0];
const height = heightValues[0];
const h = arrowHeight === 0 ? height : arrowHeight;
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h, false);
} else if (arrowHeight > 0) {
const width = widthValues[0];
const height = heightValues[0];
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height, false);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height, false);
}
if (endCap && arrowHeight === 0) {
const width = widthValues[linearSegments];
const height = heightValues[linearSegments];
// use negative height to flip the direction the cap's triangles are facing
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, -height, -height);
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height, true);
}
const addedVertexCount = (linearSegments + 1) * 8 +

View File

@@ -1,12 +1,12 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ChunkedArray } from '../../../../mol-data/util';
import { cantorPairing, ChunkedArray } from '../../../../mol-data/util';
import { MeshBuilder } from '../mesh-builder';
const normalVector = Vec3();
@@ -30,32 +30,38 @@ function add3AndScale2(out: Vec3, a: Vec3, b: Vec3, c: Vec3, sa: number, sb: num
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3normalize = Vec3.normalize;
const v3negate = Vec3.negate;
const v3copy = Vec3.copy;
const v3scaleAndAdd = Vec3.scaleAndAdd;
const v3cross = Vec3.cross;
const v3dot = Vec3.dot;
const v3unitX = Vec3.unitX;
const caAdd3 = ChunkedArray.add3;
const CosSinCache = new Map<number, { cos: number[], sin: number[] }>();
function getCosSin(radialSegments: number) {
if (!CosSinCache.has(radialSegments)) {
function getCosSin(radialSegments: number, shift: boolean) {
const offset = shift ? 1 : 0;
const hash = cantorPairing(radialSegments, offset);
if (!CosSinCache.has(hash)) {
const cos: number[] = [];
const sin: number[] = [];
for (let j = 0; j < radialSegments; ++j) {
const t = 2 * Math.PI * j / radialSegments;
const t = (j * 2 + offset) / radialSegments * Math.PI;
cos[j] = Math.cos(t);
sin[j] = Math.sin(t);
}
CosSinCache.set(radialSegments, { cos, sin });
CosSinCache.set(hash, { cos, sin });
}
return CosSinCache.get(radialSegments)!;
return CosSinCache.get(hash)!;
}
export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean) {
export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean, crossSection: 'elliptical' | 'rounded') {
const { currentGroup, vertices, normals, indices, groups } = state;
let vertexCount = vertices.elementCount;
const { cos, sin } = getCosSin(radialSegments);
const { cos, sin } = getCosSin(radialSegments, crossSection === 'rounded');
const q1 = Math.round(radialSegments / 4);
const q3 = q1 * 3;
for (let i = 0; i <= linearSegments; ++i) {
const i3 = i * 3;
@@ -65,14 +71,24 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
const width = widthValues[i];
const height = heightValues[i];
const rounded = crossSection === 'rounded' && height > width;
for (let j = 0; j < radialSegments; ++j) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]);
if (radialSegments === 2) {
v3copy(normalVector, v);
v3normalize(normalVector, normalVector);
if (j !== 0 || i % 2 === 0) v3negate(normalVector, normalVector);
if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[j], width * sin[j]);
const h = v3dot(v, v3unitX) < 0
? (j < q1 || j >= q3) ? height - width : -height + width
: (j >= q1 && j < q3) ? -height + width : height - width;
v3scaleAndAdd(surfacePoint, surfacePoint, u, h);
if (j === q1 || j === q1 - 1) {
add2AndScale2(normalVector, u, v, 0, 1);
} else if (j === q3 || j === q3 - 1) {
add2AndScale2(normalVector, u, v, 0, -1);
} else {
add2AndScale2(normalVector, u, v, cos[j], sin[j]);
}
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]);
add2AndScale2(normalVector, u, v, width * cos[j], height * sin[j]);
}
v3normalize(normalVector, normalVector);
@@ -82,19 +98,37 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
}
}
const radialSegmentsHalf = Math.round(radialSegments / 2);
for (let i = 0; i < linearSegments; ++i) {
for (let j = 0; j < radialSegments; ++j) {
// the triangles are arranged such that opposing triangles of the sheet align
// which prevents triangle intersection within tight curves
for (let j = 0; j < radialSegmentsHalf; ++j) {
caAdd3(
indices,
vertexCount + i * radialSegments + (j + 1) % radialSegments,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
vertexCount + i * radialSegments + j
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + i * radialSegments + j // b
);
caAdd3(
indices,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
vertexCount + (i + 1) * radialSegments + j,
vertexCount + i * radialSegments + j
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j // b
);
}
for (let j = radialSegmentsHalf; j < radialSegments; ++j) {
caAdd3(
indices,
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j // b
);
caAdd3(
indices,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
);
}
}
@@ -111,11 +145,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
const width = widthValues[0];
const height = heightValues[0];
let height = heightValues[0];
const rounded = crossSection === 'rounded' && height > width;
if (rounded) height -= width;
vertexCount = vertices.elementCount;
for (let i = 0; i < radialSegments; ++i) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]);
v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height);
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
}
caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
@@ -141,11 +182,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
const width = widthValues[linearSegments];
const height = heightValues[linearSegments];
let height = heightValues[linearSegments];
const rounded = crossSection === 'rounded' && height > width;
if (rounded) height -= width;
vertexCount = vertices.elementCount;
for (let i = 0; i < radialSegments; ++i) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]);
v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height);
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
}
caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);

View File

@@ -4,13 +4,15 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { MeshValues } from '../../../mol-gl/renderable/mesh';
import { createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Texture } from '../../../mol-gl/webgl/texture';
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
import { lerp } from '../../../mol-math/interpolate';
import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
import { Color } from '../../../mol-util/color';
import { ValueCell } from '../../../mol-util';
interface ColorSmoothingInput {
vertexCount: number
@@ -24,10 +26,11 @@ interface ColorSmoothingInput {
colorType: 'group' | 'groupInstance'
boundingSphere: Sphere3D
invariantBoundingSphere: Sphere3D
itemSize: 4 | 3 | 1
}
export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl?: WebGLContext, texture?: Texture) {
const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer } = input;
const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer, itemSize } = input;
const isInstanceType = colorType.endsWith('Instance');
const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
@@ -43,7 +46,6 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
const { width, height } = getVolumeTexture2dLayout(gridDim);
// console.log({ width, height, dim });
const itemSize = 3;
const data = new Float32Array(width * height * itemSize);
const count = new Float32Array(width * height);
@@ -78,10 +80,7 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
const z = Math.floor(vz);
// group colors
const ci = i * groupCount + groupBuffer[j];
const r = colors[ci * 3];
const g = colors[ci * 3 + 1];
const b = colors[ci * 3 + 2];
const ci = (i * groupCount + groupBuffer[j]) * itemSize;
// Extents of grid to consider for this atom
const begX = Math.max(0, x - p);
@@ -106,10 +105,10 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
const s = p - d;
const index = getIndex(xi, yi, zi);
data[index] += r * s;
data[index + 1] += g * s;
data[index + 2] += b * s;
count[index / 3] += s;
for (let k = 0; k < itemSize; ++k) {
data[index + k] += colors[ci + k] * s;
}
count[index / itemSize] += s;
}
}
}
@@ -117,11 +116,11 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
}
for (let i = 0, il = count.length; i < il; ++i) {
const i3 = i * 3;
const is = i * itemSize;
const c = count[i];
grid[i3] = Math.round(data[i3] / c);
grid[i3 + 1] = Math.round(data[i3 + 1] / c);
grid[i3 + 2] = Math.round(data[i3 + 2] / c);
for (let k = 0; k < itemSize; ++k) {
grid[is + k] = Math.round(data[is + k] / c);
}
}
const gridTexDim = Vec2.create(width, height);
@@ -129,12 +128,16 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
const type = isInstanceType ? 'volumeInstance' as const : 'volume' as const;
if (webgl) {
if (!texture) texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
if (!texture) {
const format = itemSize === 4 ? 'rgba' :
itemSize === 3 ? 'rgb' : 'alpha';
texture = webgl.resources.texture('image-uint8', format, 'ubyte', 'linear');
}
texture.load(textureImage);
return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
} else {
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 });
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: itemSize, outputStride: itemSize });
return {
kind: 'vertex' as const,
@@ -157,16 +160,24 @@ interface ColorInterpolationInput {
gridTexDim: Vec2
gridDim: Vec3
gridTransform: Vec4
vertexStride: number
colorStride: number
vertexStride: 3 | 4
colorStride: 1 | 3 | 4
outputStride: 1 | 3 | 4
itemOffset?: 0 | 1 | 2 | 3
}
export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input;
const itemOffset = input.itemOffset || 0;
const outputStride = input.outputStride;
if (outputStride + itemOffset > colorStride) {
throw new Error('outputStride + itemOffset must NOT be larger than colorStride');
}
const isInstanceType = input.colorType.endsWith('Instance');
const instanceCount = isInstanceType ? input.instanceCount : 1;
const image = createTextureImage(Math.max(1, instanceCount * vertexCount), 3, Uint8Array);
const image = createTextureImage(Math.max(1, instanceCount * vertexCount), outputStride, Uint8Array);
const { array } = image;
const [xn, yn] = gridDim;
@@ -204,26 +215,178 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text
const [x1, y1, z1] = v1;
const [xd, yd, zd] = vd;
const s000 = Color.fromArray(grid, getIndex(x0, y0, z0));
const s100 = Color.fromArray(grid, getIndex(x1, y0, z0));
const s001 = Color.fromArray(grid, getIndex(x0, y0, z1));
const s101 = Color.fromArray(grid, getIndex(x1, y0, z1));
const s010 = Color.fromArray(grid, getIndex(x0, y1, z0));
const s110 = Color.fromArray(grid, getIndex(x1, y1, z0));
const s011 = Color.fromArray(grid, getIndex(x0, y1, z1));
const s111 = Color.fromArray(grid, getIndex(x1, y1, z1));
const i000 = getIndex(x0, y0, z0) + itemOffset;
const i100 = getIndex(x1, y0, z0) + itemOffset;
const i001 = getIndex(x0, y0, z1) + itemOffset;
const i101 = getIndex(x1, y0, z1) + itemOffset;
const i010 = getIndex(x0, y1, z0) + itemOffset;
const i110 = getIndex(x1, y1, z0) + itemOffset;
const i011 = getIndex(x0, y1, z1) + itemOffset;
const i111 = getIndex(x1, y1, z1) + itemOffset;
const s00 = Color.interpolate(s000, s100, xd);
const s01 = Color.interpolate(s001, s101, xd);
const s10 = Color.interpolate(s010, s110, xd);
const s11 = Color.interpolate(s011, s111, xd);
const o = (i * vertexCount + j) * outputStride;
const s0 = Color.interpolate(s00, s10, yd);
const s1 = Color.interpolate(s01, s11, yd);
for (let k = 0; k < outputStride; ++k) {
const s000 = grid[i000 + k];
const s100 = grid[i100 + k];
const s001 = grid[i001 + k];
const s101 = grid[i101 + k];
const s010 = grid[i010 + k];
const s110 = grid[i110 + k];
const s011 = grid[i011 + k];
const s111 = grid[i111 + k];
Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3);
const s00 = lerp(s000, s100, xd);
const s01 = lerp(s001, s101, xd);
const s10 = lerp(s010, s110, xd);
const s11 = lerp(s011, s111, xd);
const s0 = lerp(s00, s10, yd);
const s1 = lerp(s01, s11, yd);
array[o + k] = lerp(s0, s1, zd);
}
}
}
return image;
}
//
function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
return x === 'group' || x === 'groupInstance';
}
export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
if (!isSupportedColorType(values.dColorType.ref.value)) return;
const smoothingData = calcMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionBuffer: values.aPosition.ref.value,
groupBuffer: values.aGroup.ref.value,
colorData: values.tColor.ref.value,
colorType: values.dColorType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
itemSize: 3
}, resolution, stride, webgl, colorTexture);
if (smoothingData.kind === 'volume') {
ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
ValueCell.update(values.tColorGrid, smoothingData.texture);
ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
} else if (smoothingData.kind === 'vertex') {
ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
ValueCell.update(values.tColor, smoothingData.texture);
ValueCell.update(values.uColorTexDim, smoothingData.texDim);
}
}
function isSupportedOverpaintType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyMeshOverpaintSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return;
const smoothingData = calcMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionBuffer: values.aPosition.ref.value,
groupBuffer: values.aGroup.ref.value,
colorData: values.tOverpaint.ref.value,
colorType: values.dOverpaintType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
itemSize: 4
}, resolution, stride, webgl, colorTexture);
if (smoothingData.kind === 'volume') {
ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
ValueCell.update(values.tOverpaintGrid, smoothingData.texture);
ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim);
ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform);
} else if (smoothingData.kind === 'vertex') {
ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
ValueCell.update(values.tOverpaint, smoothingData.texture);
ValueCell.update(values.uOverpaintTexDim, smoothingData.texDim);
}
}
function isSupportedTransparencyType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyMeshTransparencySmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return;
const smoothingData = calcMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionBuffer: values.aPosition.ref.value,
groupBuffer: values.aGroup.ref.value,
colorData: values.tTransparency.ref.value,
colorType: values.dTransparencyType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
itemSize: 1
}, resolution, stride, webgl, colorTexture);
if (smoothingData.kind === 'volume') {
ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
ValueCell.update(values.tTransparencyGrid, smoothingData.texture);
ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
} else if (smoothingData.kind === 'vertex') {
ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
ValueCell.update(values.tTransparency, smoothingData.texture);
ValueCell.update(values.uTransparencyTexDim, smoothingData.texDim);
}
}
function isSupportedSubstanceType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyMeshSubstanceSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
if (!isSupportedSubstanceType(values.dSubstanceType.ref.value)) return;
const smoothingData = calcMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionBuffer: values.aPosition.ref.value,
groupBuffer: values.aGroup.ref.value,
colorData: values.tSubstance.ref.value,
colorType: values.dSubstanceType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
itemSize: 3
}, resolution, stride, webgl, colorTexture);
if (smoothingData.kind === 'volume') {
ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);
ValueCell.update(values.tSubstanceGrid, smoothingData.texture);
ValueCell.update(values.uSubstanceTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uSubstanceGridDim, smoothingData.gridDim);
ValueCell.update(values.uSubstanceGridTransform, smoothingData.gridTransform);
} else if (smoothingData.kind === 'vertex') {
ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);
ValueCell.update(values.tSubstance, smoothingData.texture);
ValueCell.update(values.uSubstanceTexDim, smoothingData.texDim);
}
}

View File

@@ -27,6 +27,7 @@ import { createEmptyClipping } from '../clipping-data';
import { RenderableState } from '../../../mol-gl/renderable';
import { arraySetAdd } from '../../../mol-util/array';
import { degToRad } from '../../../mol-math/misc';
import { createEmptySubstance } from '../substance-data';
export interface Mesh {
readonly kind: 'mesh',
@@ -665,6 +666,7 @@ export namespace Mesh {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: mesh.triangleCount * 3, vertexCount: mesh.vertexCount, groupCount, instanceCount };
@@ -684,6 +686,7 @@ export namespace Mesh {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -1,18 +1,24 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../mol-util/value-cell';
import { Vec2 } from '../../mol-math/linear-algebra';
import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
import { Color } from '../../mol-util/color';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
export type OverpaintData = {
tOverpaint: ValueCell<TextureImage<Uint8Array>>
uOverpaintTexDim: ValueCell<Vec2>
dOverpaint: ValueCell<boolean>,
tOverpaintGrid: ValueCell<Texture>,
uOverpaintGridDim: ValueCell<Vec3>,
uOverpaintGridTransform: ValueCell<Vec4>,
dOverpaintType: ValueCell<string>,
}
export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) {
@@ -40,6 +46,11 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O
tOverpaint: ValueCell.create(overpaint),
uOverpaintTexDim: ValueCell.create(Vec2.create(overpaint.width, overpaint.height)),
dOverpaint: ValueCell.create(count > 0),
tOverpaintGrid: ValueCell.create(createNullTexture()),
uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dOverpaintType: ValueCell.create('groupInstance'),
};
}
}
@@ -55,6 +66,11 @@ export function createEmptyOverpaint(overpaintData?: OverpaintData): OverpaintDa
tOverpaint: ValueCell.create(emptyOverpaintTexture),
uOverpaintTexDim: ValueCell.create(Vec2.create(1, 1)),
dOverpaint: ValueCell.create(false),
tOverpaintGrid: ValueCell.create(createNullTexture()),
uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dOverpaintType: ValueCell.create('groupInstance'),
};
}
}

View File

@@ -25,6 +25,7 @@ import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
/** Point cloud */
export interface Points {
@@ -172,6 +173,7 @@ export namespace Points {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: points.pointCount, vertexCount: points.pointCount, groupCount, instanceCount };
@@ -190,6 +192,7 @@ export namespace Points {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -25,6 +25,7 @@ import { GroupMapping, createGroupMapping } from '../../util';
import { createEmptyClipping } from '../clipping-data';
import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
export interface Spheres {
readonly kind: 'spheres',
@@ -170,6 +171,7 @@ export namespace Spheres {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
@@ -191,6 +193,7 @@ export namespace Spheres {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../mol-util/value-cell';
import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
import { Material } from '../../mol-util/material';
export type SubstanceData = {
tSubstance: ValueCell<TextureImage<Uint8Array>>
uSubstanceTexDim: ValueCell<Vec2>
dSubstance: ValueCell<boolean>,
tSubstanceGrid: ValueCell<Texture>,
uSubstanceGridDim: ValueCell<Vec3>,
uSubstanceGridTransform: ValueCell<Vec4>,
dSubstanceType: ValueCell<string>,
}
export function applySubstanceMaterial(array: Uint8Array, start: number, end: number, material: Material) {
for (let i = start; i < end; ++i) {
Material.toArray(material, array, i * 3);
array[i * 3 + 2] = 255;
}
return true;
}
export function clearSubstance(array: Uint8Array, start: number, end: number) {
array.fill(0, start * 3, end * 3);
return true;
}
export function createSubstance(count: number, substanceData?: SubstanceData): SubstanceData {
const substance = createTextureImage(Math.max(1, count), 4, Uint8Array, substanceData && substanceData.tSubstance.ref.value.array);
if (substanceData) {
ValueCell.update(substanceData.tSubstance, substance);
ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(substance.width, substance.height));
ValueCell.updateIfChanged(substanceData.dSubstance, count > 0);
return substanceData;
} else {
return {
tSubstance: ValueCell.create(substance),
uSubstanceTexDim: ValueCell.create(Vec2.create(substance.width, substance.height)),
dSubstance: ValueCell.create(count > 0),
tSubstanceGrid: ValueCell.create(createNullTexture()),
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create('groupInstance'),
};
}
}
const emptySubstanceTexture = { array: new Uint8Array(4), width: 1, height: 1 };
export function createEmptySubstance(substanceData?: SubstanceData): SubstanceData {
if (substanceData) {
ValueCell.update(substanceData.tSubstance, emptySubstanceTexture);
ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(1, 1));
return substanceData;
} else {
return {
tSubstance: ValueCell.create(emptySubstanceTexture),
uSubstanceTexDim: ValueCell.create(Vec2.create(1, 1)),
dSubstance: ValueCell.create(false),
tSubstanceGrid: ValueCell.create(createNullTexture()),
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create('groupInstance'),
};
}
}

View File

@@ -29,6 +29,7 @@ import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { GroupMapping, createGroupMapping } from '../../util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
type TextAttachment = (
'bottom-left' | 'bottom-center' | 'bottom-right' |
@@ -213,6 +214,7 @@ export namespace Text {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const substance = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: text.charCount * 2 * 3, vertexCount: text.charCount * 4, groupCount, instanceCount };
@@ -235,6 +237,7 @@ export namespace Text {
...marker,
...overpaint,
...transparency,
...substance,
...clipping,
...transform,
@@ -245,7 +248,7 @@ export namespace Text {
...BaseGeometry.createValues(props, counts),
uSizeFactor: ValueCell.create(props.sizeFactor),
uBorderWidth: ValueCell.create(clamp(props.borderWidth / 2, 0, 0.5)),
uBorderWidth: ValueCell.create(clamp(props.borderWidth, 0, 0.5)),
uBorderColor: ValueCell.create(Color.toArrayNormalized(props.borderColor, Vec3.zero(), 0)),
uOffsetX: ValueCell.create(props.offsetX),
uOffsetY: ValueCell.create(props.offsetY),

View File

@@ -18,7 +18,8 @@ import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
import { accumulate_frag } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.frag';
import { accumulate_vert } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.vert';
import { TextureImage } from '../../../mol-gl/renderable/util';
import { isWebGL2 } from '../../../mol-gl/webgl/compat';
import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
export const ColorAccumulateSchema = {
drawCount: ValueSpec('number'),
@@ -38,7 +39,7 @@ export const ColorAccumulateSchema = {
tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']),
uCurrentSlice: UniformSpec('f'),
@@ -50,6 +51,7 @@ export const ColorAccumulateSchema = {
};
type ColorAccumulateValues = Values<typeof ColorAccumulateSchema>
const ColorAccumulateName = 'color-accumulate';
const ColorCountName = 'color-count';
interface AccumulateInput {
vertexCount: number
@@ -59,7 +61,7 @@ interface AccumulateInput {
instanceBuffer: Float32Array
positionTexture: Texture
groupTexture: Texture
colorData: TextureImage<Uint8Array>
colorData: Texture
colorType: 'group' | 'groupInstance'
}
@@ -96,7 +98,7 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box:
ValueCell.update(v.tPosition, input.positionTexture);
ValueCell.update(v.tGroup, input.groupTexture);
ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.width, input.colorData.height));
ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.getWidth(), input.colorData.getHeight()));
ValueCell.update(v.tColor, input.colorData);
ValueCell.updateIfChanged(v.dColorType, input.colorType);
@@ -135,7 +137,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
tPosition: ValueCell.create(input.positionTexture),
tGroup: ValueCell.create(input.groupTexture),
uColorTexDim: ValueCell.create(Vec2.create(input.colorData.width, input.colorData.height)),
uColorTexDim: ValueCell.create(Vec2.create(input.colorData.getWidth(), input.colorData.getHeight())),
tColor: ValueCell.create(input.colorData),
dColorType: ValueCell.create(input.colorType),
@@ -148,7 +150,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
};
const schema = { ...ColorAccumulateSchema };
const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag);
const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag, { drawBuffers: 'required' });
const renderItem = createComputeRenderItem(ctx, 'points', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
@@ -172,30 +174,33 @@ export const ColorNormalizeSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tCount: TextureSpec('texture', 'alpha', 'float', 'nearest'),
uTexSize: UniformSpec('v2'),
};
type ColorNormalizeValues = Values<typeof ColorNormalizeSchema>
const ColorNormalizeName = 'color-normalize';
function getNormalizeRenderable(ctx: WebGLContext, color: Texture): ComputeRenderable<ColorNormalizeValues> {
function getNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture): ComputeRenderable<ColorNormalizeValues> {
if (ctx.namedComputeRenderables[ColorNormalizeName]) {
const v = ctx.namedComputeRenderables[ColorNormalizeName].values as ColorNormalizeValues;
ValueCell.update(v.tColor, color);
ValueCell.update(v.tCount, count);
ValueCell.update(v.uTexSize, Vec2.set(v.uTexSize.ref.value, color.getWidth(), color.getHeight()));
ctx.namedComputeRenderables[ColorNormalizeName].update();
} else {
ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color);
ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color, count);
}
return ctx.namedComputeRenderables[ColorNormalizeName];
}
function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture) {
function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture) {
const values: ColorNormalizeValues = {
...QuadValues,
tColor: ValueCell.create(color),
tCount: ValueCell.create(count),
uTexSize: ValueCell.create(Vec2.create(color.getWidth(), color.getHeight())),
};
@@ -247,6 +252,9 @@ interface ColorSmoothingInput extends AccumulateInput {
}
export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl: WebGLContext, texture?: Texture) {
const { drawBuffers } = webgl.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
const isInstanceType = input.colorType.endsWith('Instance');
@@ -263,29 +271,55 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim);
// console.log({ width, height, texCols, dim, resolution });
if (!webgl.namedTextures[ColorAccumulateName]) {
webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
accumulateTexture.define(width, height);
const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
//
const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
if (!webgl.namedFramebuffers[ColorAccumulateName]) {
webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
if (isWebGL2(gl)) {
if (!webgl.namedTextures[ColorAccumulateName]) {
webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!webgl.namedTextures[ColorCountName]) {
webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'alpha', 'float', 'nearest');
}
} else {
// in webgl1 drawbuffers must be in the same format for some reason
// this is quite wasteful but good enough for medium size meshes
if (!webgl.namedTextures[ColorAccumulateName]) {
webgl.namedTextures[ColorAccumulateName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!webgl.namedTextures[ColorCountName]) {
webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
}
const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
const countTexture = webgl.namedTextures[ColorCountName];
accumulateTexture.define(width, height);
countTexture.define(width, height);
accumulateTexture.attachFramebuffer(framebuffer, 0);
countTexture.attachFramebuffer(framebuffer, 1);
const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
state.currentRenderItemId = -1;
framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,
drawBuffers.COLOR_ATTACHMENT1,
]);
const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
setAccumulateDefaults(webgl);
state.currentRenderItemId = -1;
accumulateTexture.attachFramebuffer(framebuffer, 0);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -310,21 +344,31 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
currX += dx;
}
accumulateTexture.detachFramebuffer(framebuffer, 0);
countTexture.detachFramebuffer(framebuffer, 1);
drawBuffers.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE]);
// const accImage = new Float32Array(width * height * 4);
// accumulateTexture.attachFramebuffer(framebuffer, 0);
// webgl.readPixels(0, 0, width, height, accImage);
// console.log(accImage);
// printTextureImage({ array: accImage, width, height }, 1 / 4);
// printTextureImage({ array: accImage, width, height }, { scale: 1 });
// const cntImage = new Float32Array(width * height * 4);
// countTexture.attachFramebuffer(framebuffer, 0);
// webgl.readPixels(0, 0, width, height, cntImage);
// console.log(cntImage);
// printTextureImage({ array: cntImage, width, height }, { scale: 1 });
// normalize
if (!texture) texture = resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
texture.define(width, height);
const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture);
const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture, countTexture);
state.currentRenderItemId = -1;
setNormalizeDefaults(webgl);
state.currentRenderItemId = -1;
texture.attachFramebuffer(framebuffer, 0);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
@@ -335,10 +379,160 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
// texture.attachFramebuffer(framebuffer, 0);
// webgl.readPixels(0, 0, width, height, normImage);
// console.log(normImage);
// printTextureImage({ array: normImage, width, height }, 1 / 4);
// printTextureImage({ array: normImage, width, height }, { scale: 1 });
const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor);
const type = isInstanceType ? 'volumeInstance' : 'volume';
return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
}
//
const ColorSmoothingRgbName = 'color-smoothing-rgb';
const ColorSmoothingRgbaName = 'color-smoothing-rgba';
const ColorSmoothingAlphaName = 'color-smoothing-alpha';
function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
return x === 'group' || x === 'groupInstance';
}
export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
if (!isSupportedColorType(values.dColorType.ref.value)) return;
stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
if (!webgl.namedTextures[ColorSmoothingRgbName]) {
webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
}
const colorData = webgl.namedTextures[ColorSmoothingRgbName];
colorData.load(values.tColor.ref.value);
const smoothingData = calcTextureMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionTexture: values.tPosition.ref.value,
groupTexture: values.tGroup.ref.value,
colorData,
colorType: values.dColorType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
}, resolution, stride, webgl, colorTexture);
ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
ValueCell.update(values.tColorGrid, smoothingData.texture);
ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
}
function isSupportedOverpaintType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyTextureMeshOverpaintSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return;
stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
if (!webgl.namedTextures[ColorSmoothingRgbaName]) {
webgl.namedTextures[ColorSmoothingRgbaName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const colorData = webgl.namedTextures[ColorSmoothingRgbaName];
colorData.load(values.tOverpaint.ref.value);
const smoothingData = calcTextureMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionTexture: values.tPosition.ref.value,
groupTexture: values.tGroup.ref.value,
colorData,
colorType: values.dOverpaintType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
}, resolution, stride, webgl, colorTexture);
ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
ValueCell.update(values.tOverpaintGrid, smoothingData.texture);
ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim);
ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform);
}
function isSupportedTransparencyType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyTextureMeshTransparencySmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return;
stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
if (!webgl.namedTextures[ColorSmoothingAlphaName]) {
webgl.namedTextures[ColorSmoothingAlphaName] = webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'nearest');
}
const colorData = webgl.namedTextures[ColorSmoothingAlphaName];
colorData.load(values.tTransparency.ref.value);
const smoothingData = calcTextureMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionTexture: values.tPosition.ref.value,
groupTexture: values.tGroup.ref.value,
colorData,
colorType: values.dTransparencyType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
}, resolution, stride, webgl, colorTexture);
ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
ValueCell.update(values.tTransparencyGrid, smoothingData.texture);
ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
}
function isSupportedSubstanceType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyTextureMeshSubstanceSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
if (!isSupportedSubstanceType(values.dSubstanceType.ref.value)) return;
stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
if (!webgl.namedTextures[ColorSmoothingRgbName]) {
webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
}
const colorData = webgl.namedTextures[ColorSmoothingRgbName];
colorData.load(values.tSubstance.ref.value);
const smoothingData = calcTextureMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionTexture: values.tPosition.ref.value,
groupTexture: values.tGroup.ref.value,
colorData,
colorType: values.dSubstanceType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
}, resolution, stride, webgl, colorTexture);
ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);
ValueCell.update(values.tSubstanceGrid, smoothingData.texture);
ValueCell.update(values.uSubstanceTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uSubstanceGridDim, smoothingData.gridDim);
ValueCell.update(values.uSubstanceGridTransform, smoothingData.gridTransform);
}

View File

@@ -23,6 +23,7 @@ import { createNullTexture, Texture } from '../../../mol-gl/webgl/texture';
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { createEmptySubstance } from '../substance-data';
export interface TextureMesh {
readonly kind: 'texture-mesh',
@@ -40,7 +41,7 @@ export interface TextureMesh {
readonly boundingSphere: Sphere3D
meta?: unknown
readonly meta: { [k: string]: unknown }
}
export namespace TextureMesh {
@@ -92,6 +93,7 @@ export namespace TextureMesh {
normalTexture: ValueCell.create(normalTexture),
doubleBuffer: new DoubleBuffer(),
boundingSphere: Sphere3D.clone(boundingSphere),
meta: {}
};
}
}
@@ -134,6 +136,7 @@ export namespace TextureMesh {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const substance = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount, groupCount, instanceCount };
@@ -155,6 +158,7 @@ export namespace TextureMesh {
...marker,
...overpaint,
...transparency,
...substance,
...clipping,
...transform,
@@ -165,6 +169,8 @@ export namespace TextureMesh {
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dGeoTexture: ValueCell.create(true),
meta: ValueCell.create(textureMesh.meta),
};
}

View File

@@ -1,18 +1,24 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../mol-util/value-cell';
import { Vec2 } from '../../mol-math/linear-algebra';
import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
export type TransparencyData = {
tTransparency: ValueCell<TextureImage<Uint8Array>>
uTransparencyTexDim: ValueCell<Vec2>
dTransparency: ValueCell<boolean>,
transparencyAverage: ValueCell<number>,
tTransparencyGrid: ValueCell<Texture>,
uTransparencyGridDim: ValueCell<Vec3>,
uTransparencyGridTransform: ValueCell<Vec4>,
dTransparencyType: ValueCell<string>,
}
export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) {
@@ -48,6 +54,11 @@ export function createTransparency(count: number, transparencyData?: Transparenc
uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)),
dTransparency: ValueCell.create(count > 0),
transparencyAverage: ValueCell.create(0),
tTransparencyGrid: ValueCell.create(createNullTexture()),
uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dTransparencyType: ValueCell.create('groupInstance'),
};
}
}
@@ -64,6 +75,11 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr
uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)),
dTransparency: ValueCell.create(false),
transparencyAverage: ValueCell.create(0),
tTransparencyGrid: ValueCell.create(createNullTexture()),
uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dTransparencyType: ValueCell.create('groupInstance'),
};
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Cylinders } from '../../mol-geo/geometry/cylinders/cylinders';
export function createCylinders() {
const cylinders = Cylinders.createEmpty();
const props = PD.getDefaultValues(Cylinders.Params);
const values = Cylinders.Utils.createValuesSimple(cylinders, props, ColorNames.orange, 1);
const state = Cylinders.Utils.createRenderableState(props);
return createRenderObject('cylinders', values, state, -1);
}
describe('cylinders', () => {
const ctx = tryGetGLContext(32, 32, { fragDepth: true });
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const cylinders = createCylinders();
scene.add(cylinders);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
export function createDirectVolume() {
const directVolume = DirectVolume.createEmpty();
const props = PD.getDefaultValues(DirectVolume.Params);
const values = DirectVolume.Utils.createValuesSimple(directVolume, props, ColorNames.orange, 1);
const state = DirectVolume.Utils.createRenderableState(props);
return createRenderObject('direct-volume', values, state, -1);
}
describe('direct-volume', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const directVolume = createDirectVolume();
scene.add(directVolume);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -760,8 +760,8 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
validateProgram: function () { },
generateMipmap: function () { },
isContextLost: function () { return false; },
drawingBufferWidth: 1024,
drawingBufferHeight: 1024,
drawingBufferWidth: width,
drawingBufferHeight: height,
blendColor: function () { },
blendEquation: function () { },
blendEquationSeparate: function () { },

28
src/mol-gl/_spec/gl.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createContext } from '../webgl/context';
export function getGLContext(width: number, height: number) {
const gl = require('gl')(width, height, {
alpha: true,
depth: true,
premultipliedAlpha: true,
preserveDrawingBuffer: true,
antialias: true,
});
return createContext(gl);
}
export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean }) {
try {
const ctx = getGLContext(width, height);
if (requiredExtensions?.fragDepth && !ctx.extensions.fragDepth) return;
return ctx;
} catch (e) {
return;
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Image } from '../../mol-geo/geometry/image/image';
export function createImage() {
const image = Image.createEmpty();
const props = PD.getDefaultValues(Image.Params);
const values = Image.Utils.createValuesSimple(image, props, ColorNames.orange, 1);
const state = Image.Utils.createRenderableState(props);
return createRenderObject('image', values, state, -1);
}
describe('image', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const image = createImage();
scene.add(image);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Lines } from '../../mol-geo/geometry/lines/lines';
export function createLines() {
const lines = Lines.createEmpty();
const props = PD.getDefaultValues(Lines.Params);
const values = Lines.Utils.createValuesSimple(lines, props, ColorNames.orange, 1);
const state = Lines.Utils.createRenderableState(props);
return createRenderObject('lines', values, state, -1);
}
describe('lines', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const lines = createLines();
scene.add(lines);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
export function createMesh() {
const mesh = Mesh.createEmpty();
const props = PD.getDefaultValues(Mesh.Params);
const values = Mesh.Utils.createValuesSimple(mesh, props, ColorNames.orange, 1);
const state = Mesh.Utils.createRenderableState(props);
return createRenderObject('mesh', values, state, -1);
}
describe('mesh', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const mesh = createMesh();
scene.add(mesh);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Points } from '../../mol-geo/geometry/points/points';
export function createPoints() {
const points = Points.createEmpty();
const props = PD.getDefaultValues(Points.Params);
const values = Points.Utils.createValuesSimple(points, props, ColorNames.orange, 1);
const state = Points.Utils.createRenderableState(props);
return createRenderObject('points', values, state, -1);
}
describe('points', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const points = createPoints();
scene.add(points);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -5,28 +5,14 @@
*/
import { createGl } from './gl.shim';
import { Camera } from '../../mol-canvas3d/camera';
import { Vec3, Mat4, Vec4 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { Vec3 } from '../../mol-math/linear-algebra';
import { Renderer } from '../renderer';
import { createValueColor } from '../../mol-geo/geometry/color-data';
import { createValueSize } from '../../mol-geo/geometry/size-data';
import { createContext } from '../webgl/context';
import { RenderableState } from '../renderable';
import { createRenderObject } from '../render-object';
import { PointsValues } from '../renderable/points';
import { Scene } from '../scene';
import { createEmptyMarkers } from '../../mol-geo/geometry/marker-data';
import { fillSerial } from '../../mol-util/array';
import { Color } from '../../mol-util/color';
import { Sphere3D } from '../../mol-math/geometry';
import { createEmptyOverpaint } from '../../mol-geo/geometry/overpaint-data';
import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-data';
import { createEmptyClipping } from '../../mol-geo/geometry/clipping-data';
import { createPoints } from './points.spec';
function createRenderer(gl: WebGLRenderingContext) {
export function createRenderer(gl: WebGLRenderingContext) {
const ctx = createContext(gl);
const camera = new Camera({
position: Vec3.create(0, 0, 50)
@@ -35,80 +21,14 @@ function createRenderer(gl: WebGLRenderingContext) {
return { ctx, camera, renderer };
}
function createPoints() {
const aPosition = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]));
const aGroup = ValueCell.create(fillSerial(new Float32Array(3)));
const aInstance = ValueCell.create(fillSerial(new Float32Array(1)));
const color = createValueColor(Color(0xFF0000));
const size = createValueSize(1);
const marker = createEmptyMarkers();
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const clipping = createEmptyClipping();
const aTransform = ValueCell.create(new Float32Array(16));
const m4 = Mat4.identity();
Mat4.toArray(m4, aTransform.ref.value, 0);
const transform = ValueCell.create(new Float32Array(aTransform.ref.value));
const extraTransform = ValueCell.create(new Float32Array(aTransform.ref.value));
const boundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2));
const invariantBoundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2));
const values: PointsValues = {
aPosition,
aGroup,
aTransform,
aInstance,
...color,
...marker,
...size,
...overpaint,
...transparency,
...clipping,
uAlpha: ValueCell.create(1.0),
uVertexCount: ValueCell.create(3),
uInstanceCount: ValueCell.create(1),
uGroupCount: ValueCell.create(3),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere.ref.value)),
alpha: ValueCell.create(1.0),
drawCount: ValueCell.create(3),
instanceCount: ValueCell.create(1),
matrix: ValueCell.create(m4),
transform,
extraTransform,
hasReflection: ValueCell.create(false),
boundingSphere,
invariantBoundingSphere,
uSizeFactor: ValueCell.create(1),
dPointSizeAttenuation: ValueCell.create(true),
dPointStyle: ValueCell.create('square'),
};
const state: RenderableState = {
disposed: false,
visible: true,
alphaFactor: 1,
pickable: true,
colorOnly: false,
opaque: true,
writeDepth: true,
noClip: false,
};
return createRenderObject('points', values, state, -1);
}
describe('renderer', () => {
it('basic', () => {
const [width, height] = [32, 32];
const gl = createGl(width, height, { preserveDrawingBuffer: true });
const { ctx, renderer } = createRenderer(gl);
expect(ctx.gl.canvas.width).toBe(32);
expect(ctx.gl.canvas.height).toBe(32);
expect(ctx.gl.drawingBufferWidth).toBe(32);
expect(ctx.gl.drawingBufferHeight).toBe(32);
expect(ctx.stats.resourceCounts.attribute).toBe(0);
expect(ctx.stats.resourceCounts.texture).toBe(0);
@@ -132,8 +52,8 @@ describe('renderer', () => {
scene.add(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(7);
expect(ctx.stats.resourceCounts.vertexArray).toBe(8);
expect(ctx.stats.resourceCounts.texture).toBe(8);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 8 : 0);
expect(ctx.stats.resourceCounts.program).toBe(8);
expect(ctx.stats.resourceCounts.shader).toBe(16);

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
export function createSpheres() {
const spheres = Spheres.createEmpty();
const props = PD.getDefaultValues(Spheres.Params);
const values = Spheres.Utils.createValuesSimple(spheres, props, ColorNames.orange, 1);
const state = Spheres.Utils.createRenderableState(props);
return createRenderObject('spheres', values, state, -1);
}
describe('spheres', () => {
const ctx = tryGetGLContext(32, 32, { fragDepth: true });
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const spheres = createSpheres();
scene.add(spheres);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Text } from '../../mol-geo/geometry/text/text';
export function createText() {
const text = Text.createEmpty();
const props = PD.getDefaultValues(Text.Params);
const values = Text.Utils.createValuesSimple(text, props, ColorNames.orange, 1);
const state = Text.Utils.createRenderableState(props);
return createRenderObject('text', values, state, -1);
}
describe('text', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const text = createText();
scene.add(text);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createRenderObject } from '../render-object';
import { Scene } from '../scene';
import { getGLContext, tryGetGLContext } from './gl';
import { setDebugMode } from '../../mol-util/debug';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
export function createTextureMesh() {
const textureMesh = TextureMesh.createEmpty();
const props = PD.getDefaultValues(TextureMesh.Params);
const values = TextureMesh.Utils.createValuesSimple(textureMesh, props, ColorNames.orange, 1);
const state = TextureMesh.Utils.createRenderableState(props);
return createRenderObject('texture-mesh', values, state, -1);
}
describe('texture-mesh', () => {
const ctx = tryGetGLContext(32, 32);
(ctx ? it : it.skip)('basic', async () => {
const ctx = getGLContext(32, 32);
const scene = Scene.create(ctx);
const textureMesh = createTextureMesh();
scene.add(textureMesh);
setDebugMode(true);
expect(() => scene.commit()).not.toThrow();
setDebugMode(false);
ctx.destroy();
});
});

View File

@@ -116,6 +116,9 @@ function setRenderingDefaults(ctx: WebGLContext) {
}
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
const { gl, resources, extensions } = ctx;
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
const width = pyramidTex.getWidth();
@@ -173,9 +176,6 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
ctx.state.currentRenderItemId = -1;
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,

View File

@@ -1,17 +1,19 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Texture } from '../../mol-gl/webgl/texture';
import { PrintImageOptions, printTextureImage } from '../../mol-gl/renderable/util';
import { defaults, ValueCell } from '../../mol-util';
import { ValueSpec, AttributeSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { ValueSpec, AttributeSpec, UniformSpec, Values, TextureSpec } from '../../mol-gl/renderable/schema';
import { Vec2 } from '../../mol-math/linear-algebra';
import { GLRenderingContext } from '../../mol-gl/webgl/compat';
import { PixelData } from '../../mol-util/image';
import { ShaderCode } from '../shader-code';
import { copy_frag } from '../shader/copy.frag';
import { quad_vert } from '../shader/quad.vert';
import { createComputeRenderItem } from '../webgl/render-item';
import { ComputeRenderable, createComputeRenderable } from '../renderable';
export const QuadPositions = new Float32Array([
1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // First triangle
@@ -34,21 +36,57 @@ export const QuadValues: Values<typeof QuadSchema> = {
//
function getArrayForTexture(gl: GLRenderingContext, texture: Texture, size: number) {
switch (texture.type) {
case gl.UNSIGNED_BYTE: return new Uint8Array(size);
case gl.FLOAT: return new Float32Array(size);
}
throw new Error('unknown/unsupported texture type');
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
export type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
export function createCopyRenderable(ctx: WebGLContext, texture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(texture),
uTexSize: ValueCell.create(Vec2.create(texture.getWidth(), texture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number): PixelData {
const SharedCopyName = 'shared-copy';
export function getSharedCopyRenderable(ctx: WebGLContext, texture: Texture) {
if (!ctx.namedComputeRenderables[SharedCopyName]) {
ctx.namedComputeRenderables[SharedCopyName] = createCopyRenderable(ctx, createNullTexture());
}
const copy = ctx.namedComputeRenderables[SharedCopyName] as CopyRenderable;
ValueCell.update(copy.values.tColor, texture);
ValueCell.update(copy.values.uTexSize, Vec2.set(copy.values.uTexSize.ref.value, texture.getWidth(), texture.getHeight()));
copy.update();
return copy;
}
//
const ReadTextureName = 'read-texture';
const ReadAlphaTextureName = 'read-alpha-texture';
export function readTexture(ctx: WebGLContext, texture: Texture) {
const { gl, resources } = ctx;
width = defaults(width, texture.getWidth());
height = defaults(height, texture.getHeight());
const size = width * height * 4;
const framebuffer = resources.framebuffer();
const array = getArrayForTexture(gl, texture, size);
if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
if (!ctx.namedFramebuffers[ReadTextureName]) {
ctx.namedFramebuffers[ReadTextureName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[ReadTextureName];
const width = texture.getWidth();
const height = texture.getHeight();
const array = new Uint8Array(width * height * 4);
framebuffer.bind();
texture.attachFramebuffer(framebuffer, 0);
ctx.readPixels(0, 0, width, height, array);
@@ -56,8 +94,44 @@ export function readTexture(ctx: WebGLContext, texture: Texture, width?: number,
return { array, width, height };
}
export function printTexture(ctx: WebGLContext, texture: Texture, options: Partial<PrintImageOptions> = {}) {
const pixelData = readTexture(ctx, texture);
PixelData.flipY(pixelData);
printTextureImage(pixelData, options);
export function readAlphaTexture(ctx: WebGLContext, texture: Texture) {
const { gl, state, resources } = ctx;
if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
const width = texture.getWidth();
const height = texture.getHeight();
const copy = getSharedCopyRenderable(ctx, texture);
state.currentRenderItemId = -1;
if (!ctx.namedFramebuffers[ReadAlphaTextureName]) {
ctx.namedFramebuffers[ReadAlphaTextureName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[ReadAlphaTextureName];
framebuffer.bind();
if (!ctx.namedTextures[ReadAlphaTextureName]) {
ctx.namedTextures[ReadAlphaTextureName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
}
const copyTex = ctx.namedTextures[ReadAlphaTextureName];
copyTex.define(width, height);
copyTex.attachFramebuffer(framebuffer, 0);
state.disable(gl.CULL_FACE);
state.enable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.clearColor(0, 0, 0, 0);
state.blendFunc(gl.ONE, gl.ONE);
state.blendEquation(gl.FUNC_ADD);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
copy.render();
const array = new Uint8Array(width * height * 4);
ctx.readPixels(0, 0, width, height, array);
return { array, width, height };
}

View File

@@ -10,7 +10,6 @@ import { createGraphicsRenderItem } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, InternalValues, GlobalTextureSchema } from './schema';
import { PointsShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
import { Points } from '../../mol-geo/geometry/points/points';
export const PointsSchema = {
...BaseSchema,
@@ -18,7 +17,7 @@ export const PointsSchema = {
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
dPointSizeAttenuation: DefineSpec('boolean'),
dPointStyle: DefineSpec('string', Points.StyleTypeNames),
dPointStyle: DefineSpec('string', ['square', 'circle', 'fuzzy']),
};
export type PointsSchema = typeof PointsSchema
export type PointsValues = Values<PointsSchema>

View File

@@ -141,15 +141,9 @@ export const GlobalUniformSchema = {
uClipObjectRotation: UniformSpec('v4[]'),
uClipObjectScale: UniformSpec('v3[]'),
// all the following could in principle be per object
// as a kind of 'material' parameter set
// would need to test performance implications
uLightIntensity: UniformSpec('f'),
uAmbientIntensity: UniformSpec('f'),
uMetalness: UniformSpec('f'),
uRoughness: UniformSpec('f'),
uReflectivity: UniformSpec('f'),
uLightDirection: UniformSpec('v3[]'),
uLightColor: UniformSpec('v3[]'),
uAmbientColor: UniformSpec('v3'),
uPickingAlphaThreshold: UniformSpec('f'),
@@ -224,6 +218,11 @@ export const OverpaintSchema = {
uOverpaintTexDim: UniformSpec('v2'),
tOverpaint: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
dOverpaint: DefineSpec('boolean'),
uOverpaintGridDim: UniformSpec('v3'),
uOverpaintGridTransform: UniformSpec('v4'),
tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
} as const;
export type OverpaintSchema = typeof OverpaintSchema
export type OverpaintValues = Values<OverpaintSchema>
@@ -233,10 +232,28 @@ export const TransparencySchema = {
tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
dTransparency: DefineSpec('boolean'),
transparencyAverage: ValueSpec('number'),
uTransparencyGridDim: UniformSpec('v3'),
uTransparencyGridTransform: UniformSpec('v4'),
tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
} as const;
export type TransparencySchema = typeof TransparencySchema
export type TransparencyValues = Values<TransparencySchema>
export const SubstanceSchema = {
uSubstanceTexDim: UniformSpec('v2'),
tSubstance: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dSubstance: DefineSpec('boolean'),
uSubstanceGridDim: UniformSpec('v3'),
uSubstanceGridTransform: UniformSpec('v4'),
tSubstanceGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
dSubstanceType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
} as const;
export type SubstanceSchema = typeof SubstanceSchema
export type SubstanceValues = Values<SubstanceSchema>
export const ClippingSchema = {
dClipObjectCount: DefineSpec('number'),
dClipVariant: DefineSpec('string', ['instance', 'pixel']),
@@ -253,8 +270,11 @@ export const BaseSchema = {
...MarkerSchema,
...OverpaintSchema,
...TransparencySchema,
...SubstanceSchema,
...ClippingSchema,
dLightCount: DefineSpec('number'),
aInstance: AttributeSpec('float32', 1, 1),
/**
* final per-instance transform calculated for instance `i` as
@@ -266,6 +286,9 @@ export const BaseSchema = {
* final alpha, calculated as `values.alpha * state.alpha`
*/
uAlpha: UniformSpec('f', 'material'),
uMetalness: UniformSpec('f', 'material'),
uRoughness: UniformSpec('f', 'material'),
uVertexCount: UniformSpec('i'),
uInstanceCount: UniformSpec('i'),
uGroupCount: UniformSpec('i'),

View File

@@ -7,7 +7,7 @@
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema } from './schema';
import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema, ValueSpec } from './schema';
import { MeshShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
@@ -24,6 +24,7 @@ export const TextureMeshSchema = {
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dGeoTexture: DefineSpec('boolean'),
meta: ValueSpec('unknown')
};
export type TextureMeshSchema = typeof TextureMeshSchema
export type TextureMeshValues = Values<TextureMeshSchema>

View File

@@ -91,7 +91,7 @@ export function printImageData(imageData: ImageData, options: Partial<PrintImage
}
canvas.toBlob(imgBlob => {
const objectURL = URL.createObjectURL(imgBlob);
const objectURL = URL.createObjectURL(imgBlob!);
const existingImg = document.getElementById(o.id) as HTMLImageElement;
const img = existingImg || document.createElement('img');
img.id = o.id;

View File

@@ -70,7 +70,6 @@ interface Renderer {
export const RendererParams = {
backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
// the following are general 'material' parameters
pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -85,20 +84,19 @@ export const RendererParams = {
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
style: PD.MappedStatic('matte', {
custom: PD.Group({
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
}, { isExpanded: true }),
flat: PD.Group({}),
matte: PD.Group({}),
glossy: PD.Group({}),
metallic: PD.Group({}),
plastic: PD.Group({}),
}, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
light: PD.ObjectList({
inclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
azimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
intensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
}, o => Color.toHexString(o.color), { defaultValue: [{
inclination: 180,
azimuth: 0,
color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
intensity: 0.6
}] }),
ambientColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
clip: PD.Group({
variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])),
@@ -116,44 +114,27 @@ export const RendererParams = {
};
export type RendererProps = PD.Values<typeof RendererParams>
export type Style = {
lightIntensity: number
ambientIntensity: number
metalness: number
roughness: number
reflectivity: number
type Light = {
count: number
direction: number[]
color: number[]
}
export function getStyle(props: RendererProps['style']): Style {
switch (props.name) {
case 'custom':
return props.params as Style;
case 'flat':
return {
lightIntensity: 0, ambientIntensity: 1,
metalness: 0, roughness: 0.4, reflectivity: 0.5
};
case 'matte':
return {
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 1, reflectivity: 0.5
};
case 'glossy':
return {
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 0.4, reflectivity: 0.5
};
case 'metallic':
return {
lightIntensity: 0.7, ambientIntensity: 0.7,
metalness: 0.6, roughness: 0.6, reflectivity: 0.5
};
case 'plastic':
return {
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 0.2, reflectivity: 0.5
};
const tmpDir = Vec3();
const tmpColor = Vec3();
function getLight(props: RendererProps['light'], light?: Light): Light {
const { direction, color } = light || {
direction: (new Array(5 * 3)).fill(0),
color: (new Array(5 * 3)).fill(0),
};
for (let i = 0, il = props.length; i < il; ++i) {
const p = props[i];
Vec3.directionFromSpherical(tmpDir, degToRad(p.inclination), degToRad(p.azimuth), 1);
Vec3.toArray(tmpDir, direction, i * 3);
Vec3.scale(tmpColor, Color.toVec3Normalized(tmpColor, p.color), p.intensity);
Vec3.toArray(tmpColor, color, i * 3);
}
return { count: props.length, direction, color };
}
type Clip = {
@@ -195,7 +176,7 @@ namespace Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats, extensions: { fragDepth } } = ctx;
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
const style = getStyle(p.style);
const light = getLight(p.light);
const clip = getClip(p.clip);
const viewport = Viewport();
@@ -220,6 +201,9 @@ namespace Renderer {
const cameraDir = Vec3();
const viewOffset = Vec2();
const ambientColor = Vec3();
Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
const globalUniforms: GlobalUniformValues = {
uModel: ValueCell.create(Mat4.identity()),
uView: ValueCell.create(view),
@@ -257,13 +241,9 @@ namespace Renderer {
uClipObjectRotation: ValueCell.create(clip.objects.rotation),
uClipObjectScale: ValueCell.create(clip.objects.scale),
// the following are general 'material' uniforms
uLightIntensity: ValueCell.create(style.lightIntensity),
uAmbientIntensity: ValueCell.create(style.ambientIntensity),
uMetalness: ValueCell.create(style.metalness),
uRoughness: ValueCell.create(style.roughness),
uReflectivity: ValueCell.create(style.reflectivity),
uLightDirection: ValueCell.create(light.direction),
uLightColor: ValueCell.create(light.color),
uAmbientColor: ValueCell.create(ambientColor),
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
@@ -304,6 +284,10 @@ namespace Renderer {
definesNeedUpdate = true;
}
}
if (r.values.dLightCount.ref.value !== light.count) {
ValueCell.update(r.values.dLightCount, light.count);
definesNeedUpdate = true;
}
if (definesNeedUpdate) r.update();
const program = r.getProgram(variant);
@@ -320,6 +304,10 @@ namespace Renderer {
}
if (r.values.dRenderMode) { // indicates direct-volume
if ((variant[0] === 'p' || variant === 'depth') && r.values.dRenderMode.ref.value === 'volume') {
return; // no picking/depth in volume mode
}
// culling done in fragment shader
state.disable(gl.CULL_FACE);
state.frontFace(gl.CCW);
@@ -682,14 +670,21 @@ namespace Renderer {
ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
}
if (props.style !== undefined) {
p.style = props.style;
Object.assign(style, getStyle(props.style));
ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity);
ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity);
ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness);
ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness);
ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity);
if (props.light !== undefined && !deepEqual(props.light, p.light)) {
p.light = props.light;
Object.assign(light, getLight(props.light, light));
ValueCell.update(globalUniforms.uLightDirection, light.direction);
ValueCell.update(globalUniforms.uLightColor, light.color);
}
if (props.ambientColor !== undefined && props.ambientColor !== p.ambientColor) {
p.ambientColor = props.ambientColor;
Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
}
if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
p.ambientIntensity = props.ambientIntensity;
Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
}
if (props.clip !== undefined && !deepEqual(props.clip, p.clip)) {

View File

@@ -17,7 +17,6 @@ const shaderCodeId = idFactory();
type ShaderExtensionsValue = 'required' | 'optional'
export interface ShaderExtensions {
readonly standardDerivatives?: ShaderExtensionsValue
readonly fragDepth?: ShaderExtensionsValue
readonly drawBuffers?: ShaderExtensionsValue
readonly shaderTextureLod?: ShaderExtensionsValue
@@ -101,7 +100,8 @@ const ShaderChunks: { [k: string]: string } = {
wboit_write
};
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gmi;
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm;
const reUnrollLoop = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*\+\+i\s*\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
const reSingleLineComment = /[ \t]*\/\/.*\n/g;
const reMultiLineComment = /[ \t]*\/\*[\s\S]*?\*\//g;
const reMultipleLinebreaks = /\n{2,}/g;
@@ -119,6 +119,30 @@ function addIncludes(text: string) {
.replace(reMultipleLinebreaks, '\n');
}
function unrollLoops(str: string) {
return str.replace(reUnrollLoop, loopReplacer);
}
function loopReplacer(match: string, start: string, end: string, snippet: string) {
let out = '';
for (let i = parseInt(start); i < parseInt(end); ++i) {
out += snippet
.replace(/\[\s*i\s*\]/g, `[${i}]`)
.replace(/UNROLLED_LOOP_INDEX/g, `${i}`);
}
return out;
}
function replaceCounts(str: string, defines: ShaderDefines) {
if (defines.dLightCount) str = str.replace(/dLightCount/g, `${defines.dLightCount.ref.value}`);
if (defines.dClipObjectCount) str = str.replace(/dClipObjectCount/g, `${defines.dClipObjectCount.ref.value}`);
return str;
}
function preprocess(str: string, defines: ShaderDefines) {
return unrollLoops(replaceCounts(str, defines));
}
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}, outTypes: FragOutTypes = {}): ShaderCode {
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions, outTypes };
}
@@ -139,7 +163,7 @@ export const CylindersShaderCode = ShaderCode('cylinders', cylinders_vert, cylin
import { text_vert } from './shader/text.vert';
import { text_frag } from './shader/text.frag';
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required', drawBuffers: 'optional' });
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { drawBuffers: 'optional' });
import { lines_vert } from './shader/lines.vert';
import { lines_frag } from './shader/lines.frag';
@@ -147,7 +171,7 @@ export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag, { dra
import { mesh_vert } from './shader/mesh.vert';
import { mesh_frag } from './shader/mesh.frag';
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional', drawBuffers: 'optional' });
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { drawBuffers: 'optional' });
import { directVolume_vert } from './shader/direct-volume.vert';
import { directVolume_frag } from './shader/direct-volume.frag';
@@ -185,11 +209,9 @@ function getDefinesCode(defines: ShaderDefines) {
}
function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
const prefix: string[] = [];
if (shaderExtensions.standardDerivatives) {
prefix.push('#extension GL_OES_standard_derivatives : enable');
prefix.push('#define enabledStandardDerivatives');
}
const prefix: string[] = [
'#extension GL_OES_standard_derivatives : enable'
];
if (shaderExtensions.fragDepth) {
if (extensions.fragDepth) {
prefix.push('#extension GL_EXT_frag_depth : enable');
@@ -244,9 +266,6 @@ function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExten
`layout(location = 0) out highp ${outTypes[0] || 'vec4'} out_FragData0;`
];
if (shaderExtensions.standardDerivatives) {
prefix.push('#define enabledStandardDerivatives');
}
if (shaderExtensions.fragDepth) {
prefix.push('#define enabledFragDepth');
}
@@ -278,8 +297,8 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
return {
id: shaderCodeId(),
name: shaders.name,
vert: `${vertPrefix}${header}${shaders.vert}`,
frag: `${fragPrefix}${header}${frag}`,
vert: `${vertPrefix}${header}${preprocess(shaders.vert, defines)}`,
frag: `${fragPrefix}${header}${preprocess(frag, defines)}`,
extensions: shaders.extensions,
outTypes: shaders.outTypes
};

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* adapted from three.js (https://github.com/mrdoob/three.js/)
* which under the MIT License, Copyright © 2010-2019 three.js authors
* which under the MIT License, Copyright © 2010-2021 three.js authors
*/
export const apply_light_color = `
@@ -14,21 +14,21 @@ export const apply_light_color = `
// - vec3 normal
// - float uMetalness
// - float uRoughness
// - float uReflectivity
// - float uLightIntensity
// - float uAmbientIntensity
// - vec3 uLightColor
// - vec3 uAmbientColor
// outputs
// - sets gl_FragColor
vec4 color = material;
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0));
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
PhysicalMaterial physicalMaterial;
physicalMaterial.diffuseColor = color.rgb * (1.0 - uMetalness);
physicalMaterial.specularRoughness = clamp(uRoughness, 0.04, 1.0);
physicalMaterial.specularColor = mix(vec3(0.16 * pow2(uReflectivity)), color.rgb, uMetalness);
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
physicalMaterial.roughness = max(roughness, 0.0525);
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
physicalMaterial.specularF90 = 1.0;
GeometricContext geometry;
geometry.position = -vViewPosition;
@@ -36,19 +36,28 @@ geometry.normal = normal;
geometry.viewDir = normalize(vViewPosition);
IncidentLight directLight;
directLight.direction = vec3(0.0, 0.0, -1.0);
directLight.color = vec3(uLightIntensity);
#pragma unroll_loop_start
for (int i = 0; i < dLightCount; ++i) {
directLight.direction = uLightDirection[i];
directLight.color = uLightColor[i] * PI; // * PI for punctual light
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
}
#pragma unroll_loop_end
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
vec3 irradiance = vec3(uAmbientIntensity) * PI;
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular;
// indirect specular only metals
vec3 radiance = uAmbientColor * metalness;
vec3 iblIrradiance = uAmbientColor * metalness;
vec3 clearcoatRadiance = vec3(0.0);
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
gl_FragColor = vec4(outgoingLight, color.a);
#ifdef dXrayShaded
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0, 0, 1))), uXrayEdgeFalloff);
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
#endif
`;

View File

@@ -13,11 +13,11 @@ export const assign_color_varying = `
#elif defined(dColorType_vertexInstance)
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#elif defined(dColorType_volume)
vec3 gridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb;
vec3 cgridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
#elif defined(dColorType_volumeInstance)
vec3 gridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim;
vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb;
vec3 cgridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim;
vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
#endif
#ifdef dUsePalette
@@ -25,7 +25,35 @@ export const assign_color_varying = `
#endif
#ifdef dOverpaint
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#if defined(dOverpaintType_groupInstance)
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#elif defined(dOverpaintType_vertexInstance)
vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
#elif defined(dOverpaintType_volumeInstance)
vec3 ogridPos = (uOverpaintGridTransform.w * (vModelPosition - uOverpaintGridTransform.xyz)) / uOverpaintGridDim;
vOverpaint = texture3dFrom2dLinear(tOverpaintGrid, ogridPos, uOverpaintGridDim, uOverpaintTexDim);
#endif
// pre-mix to avoid darkening due to empty overpaint
#ifdef dColorType_uniform
vOverpaint.rgb = mix(uColor.rgb, vOverpaint.rgb, vOverpaint.a);
#else
vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance)
vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_vertexInstance)
vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_volumeInstance)
vec3 sgridPos = (uSubstanceGridTransform.w * (vModelPosition - uSubstanceGridTransform.xyz)) / uSubstanceGridDim;
vSubstance = texture3dFrom2dLinear(tSubstanceGrid, sgridPos, uSubstanceGridDim, uSubstanceTexDim).rgb;
#endif
// pre-mix to avoid artifacts due to empty substance
vSubstance.rg = mix(vec2(uMetalness, uRoughness), vSubstance.rg, vSubstance.b);
#endif
#elif defined(dRenderVariant_pick)
#if defined(dRenderVariant_pickObject)
@@ -39,6 +67,14 @@ export const assign_color_varying = `
#ifdef dTransparency
vGroup = group;
vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
#if defined(dTransparencyType_groupInstance)
vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
#elif defined(dTransparencyType_vertexInstance)
vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;
#elif defined(dTransparencyType_volumeInstance)
vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;
#endif
#endif
`;

View File

@@ -3,9 +3,8 @@ export const assign_material_color = `
#if defined(dMarkerType_uniform)
float marker = uMarker;
#elif defined(dMarkerType_groupInstance)
float marker = vMarker;
float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
#endif
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
#endif
#if defined(dRenderVariant_color)
@@ -21,6 +20,13 @@ export const assign_material_color = `
#if defined(dOverpaint)
material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
float metalness = uMetalness;
float roughness = uRoughness;
#ifdef dSubstance
metalness = mix(metalness, vSubstance.r, vSubstance.b);
roughness = mix(roughness, vSubstance.g, vSubstance.b);
#endif
#elif defined(dRenderVariant_pick)
vec4 material = vColor;
#elif defined(dRenderVariant_depth)
@@ -55,6 +61,9 @@ export const assign_material_color = `
// apply screendoor transparency
#if defined(dTransparency)
float ta = 1.0 - vTransparency;
#if defined(dRenderVariant_colorWboit)
if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better with wboit
#endif
#if defined(dRenderVariant_pick)
if (ta < uPickingAlphaThreshold)

View File

@@ -1,4 +1,7 @@
export const color_frag_params = `
uniform float uMetalness;
uniform float uRoughness;
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
uniform vec3 uColor;
@@ -9,6 +12,10 @@ export const color_frag_params = `
#ifdef dOverpaint
varying vec4 vOverpaint;
#endif
#ifdef dSubstance
varying vec3 vSubstance;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100
varying vec4 vColor;

View File

@@ -1,4 +1,7 @@
export const color_vert_params = `
uniform float uMetalness;
uniform float uRoughness;
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
uniform vec3 uColor;
@@ -18,9 +21,31 @@ export const color_vert_params = `
#endif
#ifdef dOverpaint
varying vec4 vOverpaint;
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
varying vec4 vOverpaint;
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#elif defined(dOverpaintType_volumeInstance)
varying vec4 vOverpaint;
uniform vec2 uOverpaintTexDim;
uniform vec3 uOverpaintGridDim;
uniform vec4 uOverpaintGridTransform;
uniform sampler2D tOverpaintGrid;
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
varying vec3 vSubstance;
uniform vec2 uSubstanceTexDim;
uniform sampler2D tSubstance;
#elif defined(dSubstanceType_volumeInstance)
varying vec3 vSubstance;
uniform vec2 uSubstanceTexDim;
uniform vec3 uSubstanceGridDim;
uniform vec4 uSubstanceGridTransform;
uniform sampler2D tSubstanceGrid;
#endif
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100
@@ -32,9 +57,17 @@ export const color_vert_params = `
#ifdef dTransparency
varying float vGroup;
varying float vTransparency;
uniform vec2 uTransparencyTexDim;
uniform sampler2D tTransparency;
#if defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance)
varying float vTransparency;
uniform vec2 uTransparencyTexDim;
uniform sampler2D tTransparency;
#elif defined(dTransparencyType_volumeInstance)
varying float vTransparency;
uniform vec2 uTransparencyTexDim;
uniform vec3 uTransparencyGridDim;
uniform vec4 uTransparencyGridTransform;
uniform sampler2D tTransparencyGrid;
#endif
#endif
#ifdef dUsePalette

View File

@@ -89,8 +89,9 @@ export const common_clip = `
// flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
bool clipTest(vec4 sphere, int flag) {
#pragma unroll_loop_start
for (int i = 0; i < dClipObjectCount; ++i) {
if (flag == 0 || hasBit(flag, i + 1)) {
if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {
// TODO take sphere radius into account?
bool test = getSignedDistance(sphere.xyz, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i]) <= 0.0;
if ((!uClipObjectInvert[i] && test) || (uClipObjectInvert[i] && !test)) {
@@ -98,6 +99,7 @@ export const common_clip = `
}
}
}
#pragma unroll_loop_end
return false;
}
#endif

View File

@@ -1,23 +1,22 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* adapted from three.js (https://github.com/mrdoob/three.js/)
* which under the MIT License, Copyright © 2010-2019 three.js authors
* which under the MIT License, Copyright © 2010-2021 three.js authors
*/
export const light_frag_params = `
uniform float uLightIntensity;
uniform float uAmbientIntensity;
uniform float uReflectivity;
uniform float uMetalness;
uniform float uRoughness;
uniform vec3 uLightDirection[dLightCount];
uniform vec3 uLightColor[dLightCount];
uniform vec3 uAmbientColor;
struct PhysicalMaterial {
vec3 diffuseColor;
float specularRoughness;
float roughness;
vec3 specularColor;
float specularF90;
};
struct IncidentLight {
@@ -29,6 +28,7 @@ struct ReflectedLight {
vec3 directDiffuse;
vec3 directSpecular;
vec3 indirectDiffuse;
vec3 indirectSpecular;
};
struct GeometricContext {
@@ -37,20 +37,23 @@ struct GeometricContext {
vec3 viewDir;
};
vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
vec3 BRDF_Lambert(const in vec3 diffuseColor) {
return RECIPROCAL_PI * diffuseColor;
}
vec3 F_Schlick(const in vec3 f0, const in float f90, const in float dotVH) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotLH, 5.0 );
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
return (1.0 - specularColor) * fresnel + specularColor;
float fresnel = exp2((-5.55473 * dotVH - 6.98316) * dotVH);
return f0 * (1.0 - fresnel) + (f90 * fresnel);
}
// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
float V_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
float a2 = pow2(alpha);
// dotNL and dotNV are explicitly swapped. This is not a mistake.
float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
return 0.5 / max(gv + gl, EPSILON);
@@ -65,47 +68,68 @@ float D_GGX(const in float alpha, const in float dotNH) {
return RECIPROCAL_PI * a2 / pow2(denom);
}
vec3 BRDF_Diffuse_Lambert(const in vec3 diffuseColor) {
return RECIPROCAL_PI * diffuseColor;
}
// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
vec3 BRDF_GGX(const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float roughness) {
float alpha = pow2(roughness); // UE4's roughness
vec3 halfDir = normalize(incidentLight.direction + geometry.viewDir);
float dotNL = saturate(dot(geometry.normal, incidentLight.direction));
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
float dotNH = saturate(dot(geometry.normal, halfDir));
float dotLH = saturate(dot(incidentLight.direction, halfDir));
vec3 F = F_Schlick(specularColor, dotLH);
float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
vec3 halfDir = normalize( lightDir + viewDir);
float dotNL = saturate(dot(normal, lightDir));
float dotNV = saturate(dot(normal, viewDir));
float dotNH = saturate(dot(normal, halfDir));
float dotVH = saturate(dot(viewDir, halfDir));
vec3 F = F_Schlick(f0, f90, dotVH);
float V = V_GGX_SmithCorrelated(alpha, dotNL, dotNV);
float D = D_GGX(alpha, dotNH);
return F * (G * D);
return F * (V * D);
}
// ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
vec3 BRDF_Specular_GGX_Environment(const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
// Analytical approximation of the DFG LUT, one half of the
// split-sum approximation used in indirect specular lighting.
// via 'environmentBRDF' from "Physically Based Shading on Mobile"
// https://www.unrealengine.com/blog/physically-based-shading-on-mobile
vec2 DFGApprox(const in vec3 normal, const in vec3 viewDir, const in float roughness) {
float dotNV = saturate(dot(normal, viewDir));
const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
return specularColor * AB.x + AB.y;
vec2 fab = vec2(-1.04, 1.04) * a004 + r.zw;
return fab;
}
// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
// Approximates multiscattering in order to preserve energy.
// http://www.jcgt.org/published/0008/01/03/
void computeMultiscattering(const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter) {
vec2 fab = DFGApprox(normal, viewDir, roughness);
vec3 FssEss = specularColor * fab.x + specularF90 * fab.y;
float Ess = fab.x + fab.y;
float Ems = 1.0 - Ess;
vec3 Favg = specularColor + (1.0 - specularColor) * 0.047619; // 1/21
vec3 Fms = FssEss * Favg / (1.0 - Ems * Favg);
singleScatter += FssEss;
multiScatter += Fms * Ems;
}
void RE_Direct_Physical(const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
float dotNL = saturate(dot(geometry.normal, directLight.direction));
vec3 irradiance = dotNL * directLight.color;
irradiance *= PI; // punctual light
reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness);
reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
reflectedLight.directSpecular += irradiance * BRDF_GGX(directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness);
reflectedLight.directDiffuse += irradiance * BRDF_Lambert(material.diffuseColor);
}
void RE_IndirectDiffuse_Physical(const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert(material.diffuseColor);
}
void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
// Both indirect specular and indirect diffuse light accumulate here
vec3 singleScattering = vec3(0.0);
vec3 multiScattering = vec3(0.0);
vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;
computeMultiscattering(geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering);
vec3 diffuse = material.diffuseColor * (1.0 - ( singleScattering + multiScattering));
reflectedLight.indirectSpecular += radiance * singleScattering;
reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;
reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;
}
`;

View File

@@ -8,7 +8,7 @@ export const accumulate_frag = `
precision highp float;
varying vec3 vPosition;
varying vec3 vColor;
varying vec4 vColor;
uniform float uCurrentSlice;
uniform float uCurrentX;
@@ -23,6 +23,8 @@ void main() {
float dist = distance(fragPos, vPosition);
if (dist > p) discard;
gl_FragColor = vec4(vColor, 1.0) * (p - dist);
float f = p - dist;
gl_FragColor = vColor * f;
gl_FragData[1] = vec4(f);
}
`;

View File

@@ -27,7 +27,7 @@ uniform vec2 uColorTexDim;
uniform sampler2D tColor;
varying vec3 vPosition;
varying vec3 vColor;
varying vec4 vColor;
uniform vec3 uBboxSize;
uniform vec3 uBboxMin;
@@ -43,9 +43,9 @@ void main() {
gl_Position = vec4(((position - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
#if defined(dColorType_group)
vColor = readFromTexture(tColor, group, uColorTexDim).rgb;
vColor = readFromTexture(tColor, group, uColorTexDim);
#elif defined(dColorType_groupInstance)
vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim);
#endif
}
`;

View File

@@ -9,12 +9,14 @@ precision highp float;
precision highp sampler2D;
uniform sampler2D tColor;
uniform sampler2D tCount;
uniform vec2 uTexSize;
void main(void) {
vec2 coords = gl_FragCoord.xy / uTexSize;
vec4 color = texture2D(tColor, coords);
float count = texture2D(tCount, coords).r;
gl_FragColor.rgb = color.rgb / color.a;
gl_FragColor = color / count;
}
`;

View File

@@ -64,6 +64,9 @@ uniform int uMarkerPriority;
uniform sampler2D tMarker;
#endif
uniform float uMetalness;
uniform float uRoughness;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;
@@ -110,9 +113,17 @@ uniform mat4 uCartnToUnit;
#endif
#ifdef dOverpaint
varying vec4 vOverpaint;
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
uniform vec2 uSubstanceTexDim;
uniform sampler2D tSubstance;
#endif
#endif
#endif
@@ -192,6 +203,11 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
float nextValue;
vec3 color = vec3(0.45, 0.55, 0.8);
vec4 overpaint = vec4(0.0);
vec3 substance = vec3(0.0);
float metalness = uMetalness;
float roughness = uRoughness;
vec3 gradient = vec3(1.0);
vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0);
@@ -297,6 +313,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
#endif
#ifdef dOverpaint
#if defined(dOverpaintType_groupInstance)
overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
#elif defined(dOverpaintType_vertexInstance)
overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount));
#endif
color = mix(color, overpaint.rgb, overpaint.a);
#endif
// handle flipping and negative isosurfaces
#ifdef dFlipSided
bool flipped = value < uIsoValue.y; // flipped
@@ -332,6 +358,15 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
vec3 normal = -normalize(normalMatrix * normalize(gradient));
normal = normal * (float(flipped) * 2.0 - 1.0);
normal = normal * -(float(interior) * 2.0 - 1.0);
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance)
substance = readFromTexture(tSubstance, vInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_vertexInstance)
substance = texture3dFrom1dTrilinear(tSubstance, isoPos, uGridDim, uSubstanceTexDim, vInstance * float(uVertexCount)).rgb;
#endif
metalness = mix(metalness, substance.r, substance.b);
roughness = mix(roughness, substance.g, substance.b);
#endif
#include apply_light_color
#endif

View File

@@ -19,14 +19,10 @@ void main() {
#include clip_pixel
// Workaround for buggy gl_FrontFacing (e.g. on some integrated Intel GPUs)
#if defined(enabledStandardDerivatives)
vec3 fdx = dFdx(vViewPosition);
vec3 fdy = dFdy(vViewPosition);
vec3 faceNormal = normalize(cross(fdx,fdy));
bool frontFacing = dot(vNormal, faceNormal) > 0.0;
#else
bool frontFacing = dot(vNormal, vViewPosition) < 0.0;
#endif
vec3 fdx = dFdx(vViewPosition);
vec3 fdy = dFdy(vViewPosition);
vec3 faceNormal = normalize(cross(fdx,fdy));
bool frontFacing = dot(vNormal, faceNormal) > 0.0;
#if defined(dFlipSided)
interior = frontFacing;
@@ -48,7 +44,7 @@ void main() {
#ifdef dIgnoreLight
gl_FragColor = material;
#else
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
#if defined(dFlatShaded)
vec3 normal = -faceNormal;
#else
vec3 normal = -normalize(vNormal);

View File

@@ -14,10 +14,7 @@ precision highp sampler2D;
#include common_vert_params
#include color_vert_params
#include common_clip
#if defined(dColorType_grid)
#include texture3d_from_2d_linear
#endif
#include texture3d_from_2d_linear
#ifdef dGeoTexture
uniform vec2 uGeoTexDim;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,7 @@ import { idFactory } from '../../mol-util/id-factory';
import { ValueOf } from '../../mol-util/type-helpers';
import { GLRenderingContext } from './compat';
import { WebGLExtensions } from './extensions';
import { WebGLState } from './state';
const getNextBufferId = idFactory();
@@ -192,7 +193,7 @@ export interface AttributeBuffer extends Buffer {
bind: (location: number) => void
}
export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
const { instancedArrays } = extensions;
const buffer = createBuffer(gl, array, usageHint, 'attribute');
@@ -204,12 +205,12 @@ export function createAttributeBuffer<T extends ArrayType, S extends AttributeIt
gl.bindBuffer(_bufferType, buffer.getBuffer());
if (itemSize === 16) {
for (let i = 0; i < 4; ++i) {
gl.enableVertexAttribArray(location + i);
state.enableVertexAttrib(location + i);
gl.vertexAttribPointer(location + i, 4, _dataType, false, 4 * 4 * _bpe, i * 4 * _bpe);
instancedArrays.vertexAttribDivisor(location + i, divisor);
}
} else {
gl.enableVertexAttribArray(location);
state.enableVertexAttrib(location);
gl.vertexAttribPointer(location, itemSize, _dataType, false, 0, 0);
instancedArrays.vertexAttribDivisor(location, divisor);
}

View File

@@ -358,7 +358,10 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
unbindResources(gl);
// to aid GC
if (!options?.doNotForceWebGLContextLoss) gl.getExtension('WEBGL_lose_context')?.loseContext();
if (!options?.doNotForceWebGLContextLoss) {
gl.getExtension('WEBGL_lose_context')?.loseContext();
gl.getExtension('STACKGL_destroy_context')?.destroy();
}
}
};
}

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat } from './compat';
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject } from './compat';
import { isDebugMode } from '../../mol-util/debug';
export type WebGLExtensions = {
@@ -36,13 +36,11 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
if (elementIndexUint === null) {
throw new Error('Could not find support for "element_index_uint"');
}
const standardDerivatives = getStandardDerivatives(gl);
if (isDebugMode && standardDerivatives === null) {
// - non-support handled downstream (flat shading option is ignored)
// - can't be a required extension because it is not supported by `headless-gl`
console.log('Could not find support for "standard_derivatives"');
if (standardDerivatives === null) {
throw new Error('Could not find support for "standard_derivatives"');
}
const textureFloat = getTextureFloat(gl);
if (isDebugMode && textureFloat === null) {
console.log('Could not find support for "texture_float"');

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -39,12 +39,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
if (spec.type === 'attribute') {
const loc = gl.getAttribLocation(program, k);
// unused attributes will result in a `-1` location which is usually fine
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`);
locations[k] = loc;
} else if (spec.type === 'uniform' || spec.type === 'texture') {
const loc = gl.getUniformLocation(program, k);
// unused uniforms will result in a `null` location which is usually fine
// if (loc === null) console.info(`Could not get uniform location for '${k}'`)
// if (loc === null) console.info(`Could not get uniform location for '${k}'`);
locations[k] = loc as number;
}
});
@@ -192,11 +192,13 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
}
},
bindAttributes: (attributeBuffers: AttributeBuffers) => {
state.clearVertexAttribsState();
for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
const [k, buffer] = attributeBuffers[i];
const l = locations[k];
if (l !== -1) buffer.bind(l);
}
state.disableUnusedVertexAttribs();
},
bindTextures: (textures: Textures, startingTargetUnit: number) => {
for (let i = 0, il = textures.length; i < il; ++i) {

View File

@@ -67,15 +67,9 @@ function createProgramVariant(ctx: WebGLContext, variant: string, defineValues:
//
type ProgramVariants = { [k: string]: Program }
type VertexArrayVariants = { [k: string]: VertexArray | null }
type ProgramVariants = Record<string, Program>
type VertexArrayVariants = Record<string, VertexArray | null>
interface ValueChanges {
attributes: boolean
defines: boolean
elements: boolean
textures: boolean
}
function createValueChanges() {
return {
attributes: false,
@@ -84,6 +78,8 @@ function createValueChanges() {
textures: false,
};
}
type ValueChanges = ReturnType<typeof createValueChanges>
function resetValueChanges(valueChanges: ValueChanges) {
valueChanges.attributes = false;
valueChanges.defines = false;

View File

@@ -114,7 +114,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
return {
attribute: (array: ArrayType, itemSize: AttributeItemSize, divisor: number, usageHint?: UsageHint) => {
return wrap('attribute', createAttributeBuffer(gl, extensions, array, itemSize, divisor, usageHint));
return wrap('attribute', createAttributeBuffer(gl, state, extensions, array, itemSize, divisor, usageHint));
},
elements: (array: ElementsType, usageHint?: UsageHint) => {
return wrap('elements', createElementsBuffer(gl, array, usageHint));
@@ -133,7 +133,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
return wrap('texture', createTexture(gl, extensions, kind, format, type, filter));
},
vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => {
return wrap('vertexArray', createVertexArray(extensions, program, attributeBuffers, elementsBuffer));
return wrap('vertexArray', createVertexArray(gl, extensions, program, attributeBuffers, elementsBuffer));
},
getByteCounts: () => {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -59,11 +59,15 @@ export type WebGLState = {
/** set the RGB blend equation and alpha blend equation separately, determines how a new pixel is combined with an existing */
blendEquationSeparate: (modeRGB: number, modeAlpha: number) => void
enableVertexAttrib: (index: number) => void
clearVertexAttribsState: () => void
disableUnusedVertexAttribs: () => void
reset: () => void
}
export function createState(gl: GLRenderingContext): WebGLState {
let enabledCapabilities: { [k: number]: boolean } = {};
let enabledCapabilities: Record<number, boolean> = {};
let currentFrontFace = gl.getParameter(gl.FRONT_FACE);
let currentCullFace = gl.getParameter(gl.CULL_FACE_MODE);
@@ -79,6 +83,16 @@ export function createState(gl: GLRenderingContext): WebGLState {
let currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
let currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
let maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
const vertexAttribsState: number[] = [];
const clearVertexAttribsState = () => {
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
}
};
clearVertexAttribsState();
return {
currentProgramId: -1,
currentMaterialId: -1,
@@ -168,6 +182,17 @@ export function createState(gl: GLRenderingContext): WebGLState {
}
},
enableVertexAttrib: (index: number) => {
gl.enableVertexAttribArray(index);
vertexAttribsState[index] = 1;
},
clearVertexAttribsState,
disableUnusedVertexAttribs: () => {
for (let i = 0; i < maxVertexAttribs; ++i) {
if (vertexAttribsState[i] === 0) gl.disableVertexAttribArray(i);
}
},
reset: () => {
enabledCapabilities = {};
@@ -184,6 +209,12 @@ export function createState(gl: GLRenderingContext): WebGLState {
currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
vertexAttribsState.length = 0;
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
}
}
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -8,6 +8,7 @@ import { Program } from './program';
import { ElementsBuffer, AttributeBuffers } from './buffer';
import { WebGLExtensions } from './extensions';
import { idFactory } from '../../mol-util/id-factory';
import { GLRenderingContext } from './compat';
const getNextVertexArrayId = idFactory();
@@ -40,7 +41,7 @@ export interface VertexArray {
destroy: () => void
}
export function createVertexArray(extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
export function createVertexArray(gl: GLRenderingContext, extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
const id = getNextVertexArrayId();
let vertexArray = getVertexArray(extensions);
let vertexArrayObject = getVertexArrayObject(extensions);
@@ -68,6 +69,13 @@ export function createVertexArray(extensions: WebGLExtensions, program: Program,
},
destroy: () => {
if (destroyed) return;
if (elementsBuffer) {
// workaround for ANGLE/Chromium bug
// - https://bugs.chromium.org/p/angleproject/issues/detail?id=6599
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1272238
vertexArrayObject.bindVertexArray(vertexArray);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
vertexArrayObject.deleteVertexArray(vertexArray);
destroyed = true;
}

View File

@@ -332,6 +332,80 @@ M END
$$$$
`;
const V3000SdfString = `FYI-001
FYICenter.com
123456789012345678901234567890123456789012345678901234567890
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 13 14 0 0 0
M V30 BEGIN ATOM
M V30 1 N 0.84 -0.16 0 0
M V30 2 N 1.48 0.43 0 0
M V30 3 N 0.09 0.27 0 0
M V30 4 C 1.11 1.21 0 0
M V30 5 C 0.27 1.12 0 0
M V30 6 C 0.84 -1.03 0 0
M V30 7 C 1.53 1.99 0 0
M V30 8 Cl 1.07 2.74 0.01 0
M V30 9 C 1.59 -1.46 0 0
M V30 10 C 0.08 -1.46 0 0
M V30 11 C 1.59 -2.33 0 0
M V30 12 C 0.07 -2.32 0 0
M V30 13 C 0.84 -2.76 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 2 1
M V30 2 1 3 1
M V30 3 1 6 1
M V30 4 2 4 2
M V30 5 2 5 3
M V30 6 1 7 4
M V30 7 1 4 5
M V30 8 1 9 6
M V30 9 2 10 6
M V30 10 1 8 7
M V30 11 2 11 9
M V30 12 1 12 10
M V30 13 1 13 11
M V30 14 2 13 12
M V30 END BOND
M V30 END CTAB
M END
> <Comment>
This is an SDF example.
With a multi-line comment.
> <source>
This was retrieved from biotech.fyicenter.com
$$$$
L-Alanine
GSMACCS-II07189510252D 1 0.00366 0.00000 0
Figure 1, J. Chem. Inf. Comput. Sci., Vol 32, No. 3., 1992
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 6 5 0 0 1
M V30 BEGIN ATOM
M V30 1 C -0.6622 0.5342 0 0 CFG=2
M V30 2 C 0.6622 -0.3 0 0
M V30 3 C -0.7207 2.0817 0 0 MASS=13
M V30 4 N -1.8622 -0.3695 0 0 CHG=1
M V30 5 O 0.622 -1.8037 0 0
M V30 6 O 1.9464 0.4244 0 0 CHG=-1
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 1 3 CFG=1
M V30 3 1 1 4
M V30 4 2 2 5
M V30 5 1 2 6
M V30 END BOND
M V30 END CTAB
M END
$$$$
`;
describe('sdf reader', () => {
it('basic', async () => {
const parsed = await parseSdf(SdfString).run();
@@ -383,4 +457,58 @@ describe('sdf reader', () => {
expect(compound3.dataItems.dataHeader.value(21)).toBe('<PUBCHEM_COORDINATE_TYPE>');
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
});
it('v3000', async () => {
const parsed = await parseSdf(V3000SdfString).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
expect(parsed.result.compounds.length).toBe(2);
const compound1 = parsed.result.compounds[0];
expect(compound1.molFile.atoms.count).toBe(13);
expect(compound1.molFile.atoms.x.rowCount).toBe(13);
expect(compound1.molFile.atoms.y.rowCount).toBe(13);
expect(compound1.molFile.atoms.z.rowCount).toBe(13);
expect(compound1.molFile.atoms.type_symbol.rowCount).toBe(13);
expect(compound1.molFile.bonds.count).toBe(14);
expect(compound1.molFile.bonds.atomIdxA.rowCount).toBe(14);
expect(compound1.molFile.bonds.atomIdxB.rowCount).toBe(14);
expect(compound1.molFile.bonds.order.rowCount).toBe(14);
expect(compound1.molFile.atoms.x.value(7)).toBe(1.07);
expect(compound1.molFile.atoms.y.value(7)).toBe(2.74);
expect(compound1.molFile.atoms.z.value(7)).toBe(0.01);
expect(compound1.molFile.atoms.type_symbol.value(7)).toBe('Cl');
expect(compound1.molFile.bonds.atomIdxA.value(10)).toBe(11);
expect(compound1.molFile.bonds.atomIdxB.value(10)).toBe(9);
expect(compound1.molFile.bonds.order.value(10)).toBe(2);
expect(compound1.dataItems.dataHeader.rowCount).toBe(2);
expect(compound1.dataItems.data.rowCount).toBe(2);
expect(compound1.dataItems.dataHeader.value(0)).toBe('<Comment>');
expect(compound1.dataItems.data.value(0)).toBe(`This is an SDF example.\nWith a multi-line comment.`);
expect(compound1.dataItems.dataHeader.value(1)).toBe('<source>');
expect(compound1.dataItems.data.value(1)).toBe('This was retrieved from biotech.fyicenter.com');
const compound2 = parsed.result.compounds[1];
expect(compound2.molFile.atoms.count).toBe(6);
expect(compound2.molFile.bonds.count).toBe(5);
expect(compound2.molFile.atoms.x.value(4)).toBe(0.622);
expect(compound2.molFile.atoms.y.value(4)).toBe(-1.8037);
expect(compound2.molFile.atoms.z.value(4)).toBe(0);
expect(compound2.molFile.atoms.type_symbol.value(4)).toBe('O');
expect(compound2.molFile.bonds.atomIdxA.value(1)).toBe(1);
expect(compound2.molFile.bonds.atomIdxB.value(1)).toBe(3);
expect(compound2.molFile.bonds.order.value(1)).toBe(1);
expect(compound2.dataItems.dataHeader.rowCount).toBe(0);
expect(compound2.dataItems.data.rowCount).toBe(0);
});
});

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
/**
* An identifier for the wwPDB site creating or modifying the molecule.
*/
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
processing_site: Aliased<'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
/**
* The action associated with this audit record.
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -114,7 +114,7 @@ export const CifCore_Schema = {
* Abstracts. This ordering is used in _chemical_formula.moiety
* and _chemical_formula.sum.
*
* _chemical_formula.iupac '[Mo (C O)4 (C18 H33 P)2]'
* _chemical_formula.IUPAC '[Mo (C O)4 (C18 H33 P)2]'
* _chemical_formula.moiety 'C40 H66 Mo O4 P2'
* _chemical_formula.structural '((C O)4 (P (C6 H11)3)2)Mo'
* _chemical_formula.sum 'C40 H66 Mo O4 P2'
@@ -146,7 +146,7 @@ export const CifCore_Schema = {
sum: str,
/**
* Mass corresponding to the formulae _chemical_formula.structural,
* *_iupac, *_moiety or *_sum and, together with the Z value and cell
* *_IUPAC, *_moiety or *_sum and, together with the Z value and cell
* parameters yield the density given as _exptl_crystal.density_diffrn.
*/
weight: float,
@@ -478,7 +478,7 @@ export const CifCore_Schema = {
* A concatenated series of single-letter codes which indicate the
* refinement restraints or constraints applied to this site. This
* item should not be used. It has been replaced by
* _atom_site.refinement_flags_posn, _adp and _occupancy. It is
* _atom_site.refinement_flags_posn, _ADP and _occupancy. It is
* retained in this dictionary only to provide compatibility with
* legacy CIFs.
*/
@@ -505,9 +505,9 @@ export const CifCore_Schema = {
* atomic displacement parameter, U(equiv), in angstroms squared,
* calculated from anisotropic atomic displacement parameters.
*
* U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~ a~j~)]
* U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~.a~j~)]
*
* a = the real-space cell lengths
* a = the real-space cell vectors
* a* = the reciprocal-space cell lengths
* Ref: Fischer, R. X. & Tillmanns, E. (1988). Acta Cryst. C44, 775-776.
*/
@@ -683,12 +683,21 @@ export const CifCore_Aliases = {
'geom_bond.distance': [
'geom_bond_dist',
],
'audit.block_doi': [
'audit_block_DOI',
],
'database_code.cod': [
'database_code_COD',
],
'database_code.csd': [
'database_code_CSD',
],
'database_code.depnum_ccdc_archive': [
'database_code_depnum_CCDC_archive',
],
'database_code.depnum_ccdc_fiz': [
'database_code_depnum_CCDC_fiz',
],
'database_code.icsd': [
'database_code_ICSD',
],
@@ -699,6 +708,7 @@ export const CifCore_Aliases = {
'database_code_NBS',
],
'atom_site.adp_type': [
'atom_site_ADP_type',
'atom_site_thermal_displace_type',
],
'atom_site.label': [

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -0,0 +1,109 @@
import { Column } from '../../../mol-data/db';
import { MolFile } from '../mol/parser';
import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
export function isV3(
versionLine: string
): boolean {
return versionLine.trim().endsWith('V3000');
}
export function handleCountsV3(
tokenizer: Tokenizer
): { atomCount: number, bondCount: number } {
const atomCount = TokenBuilder.create(tokenizer.data, 1);
const bondCount = TokenBuilder.create(tokenizer.data, 1);
Tokenizer.eatLine(tokenizer); // BEGIN CTAB
skipSingleValue(tokenizer); // M
skipSingleValue(tokenizer); // V30
skipSingleValue(tokenizer); // COUNTS
addSingleValue(tokenizer, atomCount);
addSingleValue(tokenizer, bondCount);
Tokenizer.eatLine(tokenizer);
return {
atomCount: TokenColumn(atomCount)(Column.Schema.int).value(0),
bondCount: TokenColumn(bondCount)(Column.Schema.int).value(0)
};
}
export function handleAtomsV3(
tokenizer: Tokenizer,
atomCount: number
): MolFile['atoms'] {
const x = TokenBuilder.create(tokenizer.data, atomCount * 2);
const y = TokenBuilder.create(tokenizer.data, atomCount * 2);
const z = TokenBuilder.create(tokenizer.data, atomCount * 2);
const type_symbol = TokenBuilder.create(tokenizer.data, atomCount * 2);
for (let i = 0; i < atomCount; ++i) {
Tokenizer.markLine(tokenizer);
skipSingleValue(tokenizer); // M
skipSingleValue(tokenizer); // V30
skipSingleValue(tokenizer); // Index
const { position } = tokenizer;
addSingleValue(tokenizer, type_symbol);
addSingleValue(tokenizer, x);
addSingleValue(tokenizer, y);
addSingleValue(tokenizer, z);
tokenizer.position = position;
}
Tokenizer.eatLine(tokenizer); // Previous Line
Tokenizer.eatLine(tokenizer); // END ATOM
return {
count: atomCount,
x: TokenColumn(x)(Column.Schema.float),
y: TokenColumn(y)(Column.Schema.float),
z: TokenColumn(z)(Column.Schema.float),
type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
};
}
export function handleBondsV3(
tokenizer: Tokenizer,
bondCount: number
): MolFile['bonds'] {
const atomIdxA = TokenBuilder.create(tokenizer.data, bondCount * 2);
const atomIdxB = TokenBuilder.create(tokenizer.data, bondCount * 2);
const order = TokenBuilder.create(tokenizer.data, bondCount * 2);
for (let i = 0; i < bondCount; ++i) {
Tokenizer.markLine(tokenizer);
skipSingleValue(tokenizer); // M
skipSingleValue(tokenizer); // V30
skipSingleValue(tokenizer); // Index
const { position } = tokenizer;
addSingleValue(tokenizer, order);
addSingleValue(tokenizer, atomIdxA);
addSingleValue(tokenizer, atomIdxB);
tokenizer.position = position;
}
Tokenizer.eatLine(tokenizer); // Previous Line
Tokenizer.eatLine(tokenizer); // END BOND
return {
count: bondCount,
atomIdxA: TokenColumn(atomIdxA)(Column.Schema.float),
atomIdxB: TokenColumn(atomIdxB)(Column.Schema.float),
order: TokenColumn(order)(Column.Schema.float),
};
}
function skipSingleValue(tokenizer: Tokenizer) {
Tokenizer.skipWhitespace(tokenizer);
Tokenizer.eatValue(tokenizer);
}
function addSingleValue(tokenizer: Tokenizer, tokens: Tokens) {
const { position: valueStart } = tokenizer;
Tokenizer.skipWhitespace(tokenizer);
Tokenizer.eatValue(tokenizer);
Tokenizer.trim(tokenizer, valueStart, tokenizer.position);
TokenBuilder.addUnchecked(tokens, tokenizer.tokenStart, tokenizer.tokenEnd);
}

View File

@@ -11,8 +11,9 @@ import { Task } from '../../../mol-task';
import { ReaderResult as Result } from '../result';
import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
import { handleAtomsV3, handleBondsV3, handleCountsV3, isV3 } from './parser-v3-util';
/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 */
/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 & 79 */
export interface SdfFileCompound {
readonly molFile: MolFile,
@@ -66,14 +67,22 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
};
}
function handleCountsV2(countsAndVersion: string): { atomCount: number, bondCount: number } {
return {
atomCount: +countsAndVersion.substr(0, 3),
bondCount: +countsAndVersion.substr(3, 3)
};
}
function handleMolFile(tokenizer: Tokenizer) {
const title = Tokenizer.readLine(tokenizer).trim();
const program = Tokenizer.readLine(tokenizer).trim();
const comment = Tokenizer.readLine(tokenizer).trim();
const counts = Tokenizer.readLine(tokenizer);
const countsAndVersion = Tokenizer.readLine(tokenizer);
const molIsV3 = isV3(countsAndVersion);
const atomCount = +counts.substr(0, 3), bondCount = +counts.substr(3, 3);
const { atomCount, bondCount } = molIsV3 ? handleCountsV3(tokenizer) : handleCountsV2(countsAndVersion);
if (Number.isNaN(atomCount) || Number.isNaN(bondCount)) {
// try to skip to next molecule
@@ -84,8 +93,8 @@ function handleMolFile(tokenizer: Tokenizer) {
return;
}
const atoms = handleAtoms(tokenizer, atomCount);
const bonds = handleBonds(tokenizer, bondCount);
const atoms = molIsV3 ? handleAtomsV3(tokenizer, atomCount) : handleAtoms(tokenizer, atomCount);
const bonds = molIsV3 ? handleBondsV3(tokenizer, bondCount) : handleBonds(tokenizer, bondCount);
const dataItems = handleDataItems(tokenizer);
return {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -48,7 +48,7 @@ namespace Axes3D {
return out;
}
const tmpTransformMat3 = Mat3.zero();
const tmpTransformMat3 = Mat3();
/** Transform axes with a Mat4 */
export function transform(out: Axes3D, a: Axes3D, m: Mat4): Axes3D {
Vec3.transformMat4(out.origin, a.origin, m);
@@ -58,6 +58,13 @@ namespace Axes3D {
Vec3.transformMat3(out.dirC, a.dirC, n);
return out;
}
export function scale(out: Axes3D, a: Axes3D, scale: number): Axes3D {
Vec3.scale(out.dirA, a.dirA, scale);
Vec3.scale(out.dirB, a.dirB, scale);
Vec3.scale(out.dirC, a.dirC, scale);
return out;
}
}
export { Axes3D };

View File

@@ -504,6 +504,19 @@ namespace Vec3 {
return dot(tmp_dh_cb, tmp_dh_cross) > 0 ? _angle : -_angle;
}
/**
* @param inclination in radians [0, PI]
* @param azimuth in radians [0, 2 * PI]
* @param radius [0, +Inf]
*/
export function directionFromSpherical(out: Vec3, inclination: number, azimuth: number, radius: number): Vec3 {
return Vec3.set(out,
radius * Math.cos(azimuth) * Math.sin(inclination),
radius * Math.sin(azimuth) * Math.sin(inclination),
radius * Math.cos(inclination)
);
}
/**
* Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
*/

View File

@@ -25,7 +25,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
let currentSpec: Spec | undefined;
let currentCompound: EntityCompound = { chains: [], description: '' };
const Compounds: EntityCompound[] = [];
const compounds: EntityCompound[] = [];
for (let i = lineStart; i < lineEnd; i++) {
const line = getLine(i);
@@ -55,7 +55,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
chains: [],
description: ''
};
Compounds.push(currentCompound);
compounds.push(currentCompound);
} else if (currentSpec === 'MOLECULE') {
if (currentCompound.description) currentCompound.description += ' ';
currentCompound.description += value;
@@ -64,7 +64,31 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
}
}
return Compounds;
// Define a seprate entity for each chain
// --------------------------------------
//
// This is a workaround for how sequences are currently determined for PDB files.
//
// The current approach infers the "observed sequence" from the atomic hierarchy.
// However, for example for PDB ID 3HHR, this approach fails, since chains B and C
// belong to the same entity but contain different observed sequence, which causes display
// errors in the sequence viewer (since the sequences are determined "per entity").
//
// A better approach could be to parse SEQRES categories and use it to construct
// entity_poly_seq category. However, this would require constructing label_seq_id (with gaps)
// from RES ID pdb column (auth_seq_id), which isn't a trivial exercise.
//
// (properly formatted) mmCIF structures do not exhibit this issue.
const singletons: EntityCompound[] = [];
for (const comp of compounds) {
for (const chain of comp.chains) {
singletons.push({
description: comp.description,
chains: [chain]
});
}
}
return singletons;
}
export function parseHetnam(lines: Tokens, lineStart: number, lineEnd: number) {

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