Compare commits

...

93 Commits

Author SHA1 Message Date
Alexander Rose
4d0f0ceebf 3.0.0-dev.3 2021-12-04 22:23:51 -08:00
Alexander Rose
14abcddfcf changelog 2021-12-04 22:17:24 -08:00
Alexander Rose
704cc96a9f Merge pull request #300 from sukolsak/fix-export
Fix USDZ and OBJ export
2021-12-04 22:13:41 -08:00
Sukolsak Sakshuwong
b51f610173 fix USDZ and OBJ export 2021-12-04 20:08:15 -08:00
dsehnal
470280ea1a changelog and update eslint 2021-12-01 13:29:02 +01:00
dsehnal
dafb5a8299 3.0.0-dev.2 2021-12-01 13:26:08 +01:00
dsehnal
4a1af03744 package.json fix 2021-12-01 13:23:50 +01:00
dsehnal
bb176f1efb 3.0.0-dev.1 2021-12-01 13:17:15 +01:00
dsehnal
a53bcde973 npmignore tweaks 2021-12-01 13:14:28 +01:00
David Sehnal
1a8dc2c637 Merge pull request #295 from molstar/material-object
Refactor Material Representation
2021-11-29 18:04:52 +01:00
dsehnal
f96211ff91 comment 2021-11-29 18:04:23 +01:00
dsehnal
77f9c02785 udpate presets 2021-11-29 18:01:41 +01:00
dsehnal
7910b65fdc Merge branch 'master' of https://github.com/molstar/molstar 2021-11-29 14:49:53 +01:00
dsehnal
eb4fc4588d remove console.log 2021-11-29 14:49:51 +01:00
David Sehnal
5430674071 Merge pull request #294 from MadCatX/fixtypo
Fix typo in README
2021-11-29 14:10:20 +01:00
dsehnal
17e67e3b79 Refactor Material representation 2021-11-29 14:09:56 +01:00
dsehnal
e87a0d72e4 Fix docking viewer material usage 2021-11-29 12:48:26 +01:00
dsehnal
67d3c65907 ParamDefinition.Group.presets support 2021-11-29 12:35:57 +01:00
dsehnal
564a5486c9 StructureComponentManager.Options state saving support 2021-11-29 11:57:37 +01:00
Michal Malý
9ce11c4c32 Fix typo in README 2021-11-29 11:41:57 +01:00
dsehnal
5e97b551a5 changelog 2021-11-29 11:10:30 +01:00
David Sehnal
77536e75af Merge pull request #292 from MadCatX/provide-rebuild-cmd
Add packaging command to force a full rebuild
2021-11-29 11:05:44 +01:00
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
Michal Malý
3a6ab55266 Add a cleanup packaging script 2021-11-28 22:29:50 +01: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
Michal Malý
aaec452bc2 Add packaging command to force a full rebuild 2021-11-28 13:28:06 +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
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
e5dcc8e54f wip, lighting improvements
- set light direction
- set light color
- set ambient color
2021-10-31 14:59:19 -07:00
118 changed files with 4028 additions and 3373 deletions

View File

@@ -1 +1,5 @@
tsconfig.commonjs.tsbuildinfo
tests
perf-tests
_spec
*.tsbuildinfo
*.js.map

View File

@@ -6,6 +6,53 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.0.0-dev.3] - 2021-12-4
- Fix OBJ and USDZ export
## [v3.0.0-dev.2] - 2021-12-1
- Do not include tests and source maps in NPM package
## [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
- ``SimpleSettingsParams.lighting.renderStyle`` and ``RendererParams.style`` were removed
- Add substance theme with per-group material rendering properties
- ``StructureComponentManager.Options`` state saving support
- ``ParamDefinition.Group.presets`` support
## [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)

View File

@@ -68,6 +68,17 @@ If working on just the viewer, ``npm run watch-viewer`` will provide shorter com
Debug/production mode in browsers can be turned on/off during runtime by calling ``setMolStarDebugMode(true/false, true/false)`` from the dev console.
### Cleaning and forcing a full rebuild
npm run clean
Wipes the `build` and `lib` directories and `.tsbuildinfo` files.
npm run rebuild
Runs the cleanup script prior to building the project, forcing a full rebuild of the project.
Use these commands to resolve occassional build failures which may arise after some dependency updates. Once done, `npm run build` should work again. Note that full rebuilds take more time to complete.
### Build for production:
NODE_ENV=production npm run build

3722
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.6",
"version": "3.0.0-dev.3",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -16,6 +16,8 @@
"test": "npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"clean": "node ./scripts/clean.js",
"rebuild": "npm run clean && npm run build",
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
@@ -36,7 +38,7 @@
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js --working-folder ./build/state --port 1339",
"preversion": "npm run test",
"version": "npm run build",
"version": "npm run rebuild && cpx .npmignore lib/",
"postversion": "git push && git push --tags"
},
"files": [
@@ -89,52 +91,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",
"@types/gl": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.32.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.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",
@@ -142,13 +143,13 @@
"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"

19
scripts/clean.js Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <malym@ibt.cas.cz>
*/
const fs = require('fs');
const path = require('path');
const toClean = [
path.resolve(__dirname, '../build'),
path.resolve(__dirname, '../lib'),
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
];
toClean.forEach(ph => {
if (fs.existsSync(ph))
fs.rm(ph, { recursive: true }, err => { if (err) console.warn(`Failed to delete ${ph}: ${err}`); });
});

View File

@@ -21,12 +21,12 @@ import { PluginContext } from '../../mol-plugin/context';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateObjectRef } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { Material } from '../../mol-util/material';
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 +40,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,
@@ -77,7 +76,7 @@ const PresetParams = {
...StructureRepresentationPresetProvider.CommonParams,
};
const CustomMaterial = Material({ roughness: 0.2, metalness: 0 });
export const StructurePreset = StructureRepresentationPresetProvider({
id: 'preset-structure',
@@ -94,8 +93,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, material: CustomMaterial, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, material: CustomMaterial }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -121,8 +120,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 +148,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, material: CustomMaterial, 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, material: CustomMaterial, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -177,8 +176,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, material: CustomMaterial, 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, material: CustomMaterial, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
};
await update.commit({ revertOnError: true });
@@ -206,10 +205,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, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
};
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

@@ -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, 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, 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) {
@@ -65,11 +103,16 @@ export namespace BaseGeometry {
uVertexCount: ValueCell.create(counts.vertexCount),
uGroupCount: ValueCell.create(counts.groupCount),
drawCount: ValueCell.create(counts.drawCount),
uMetalness: ValueCell.create(props.material.metalness),
uRoughness: ValueCell.create(props.material.roughness),
dLightCount: ValueCell.create(1),
};
}
export function updateValues(values: BaseValues, props: PD.Values<Params>) {
ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
ValueCell.updateIfChanged(values.uMetalness, props.material.metalness);
ValueCell.updateIfChanged(values.uRoughness, props.material.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

@@ -52,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

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

@@ -20,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)
@@ -54,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

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

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

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Fred Ludlow <Fred.Ludlow@astx.com>
@@ -68,11 +68,8 @@ function addUnitHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder:
const { totalH } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const { elementAromaticRingIndices } = unit.rings;
for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
if (elementAromaticRingIndices.has(i)) continue;
const element = typeSymbol(unit, i);
if ((
// include both nitrogen atoms in histidine due to
@@ -134,15 +131,12 @@ function addUnitHydrogenAcceptors(structure: Structure, unit: Unit.Atomic, build
const { charge, implicitH, idealGeometry } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const { elementAromaticRingIndices } = unit.rings;
const add = (i: StructureElement.UnitIndex) => {
builder.add(FeatureType.HydrogenAcceptor, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
};
for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
if (elementAromaticRingIndices.has(i)) continue;
const element = typeSymbol(unit, i);
if (element === Elements.O) {
// Basically assume all oxygen atoms are acceptors!

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 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 David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -99,12 +99,12 @@ const residue = {
secondary_structure_type: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
return secStruc ? secStruc.type[secStruc.getIndex(l.unit.residueIndex[l.element])] : SecondaryStructureType.Flag.NA;
}),
secondary_structure_key: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
return secStruc ? secStruc.key[secStruc.getIndex(l.unit.residueIndex[l.element])] : -1;
}),
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
};

View File

@@ -355,8 +355,8 @@ class Structure {
return this.models.indexOf(m);
}
remapModel(m: Model) {
const { dynamicBonds, interUnitBonds } = this.state;
remapModel(m: Model): Structure {
const { dynamicBonds, interUnitBonds, parent } = this.state;
const units: Unit[] = [];
for (const ug of this.unitSymmetryGroups) {
const unit = ug.units[0].remapModel(m, dynamicBonds);
@@ -367,6 +367,7 @@ class Structure {
}
}
return Structure.create(units, {
parent: parent?.remapModel(m),
label: this.label,
interUnitBonds: dynamicBonds ? undefined : interUnitBonds,
dynamicBonds

View File

@@ -53,7 +53,8 @@ export const OpenFiles = StateAction.build({
await provider.visuals?.(plugin, parsed);
}
} catch (e) {
plugin.log.error(e);
console.error(e);
plugin.log.error(`Error opening file '${file.name}'`);
}
}
}).runInContext(taskCtx);
@@ -88,7 +89,8 @@ export const DownloadFile = StateAction.build({
await provider.visuals?.(plugin, parsed);
}
} catch (e) {
plugin.log.error(e);
console.error(e);
plugin.log.error(`Error downloading '${typeof params.url === 'string' ? params.url : params.url.url}'`);
}
}).runInContext(taskCtx);
}));

View File

@@ -33,7 +33,9 @@ async function tryObtainRecommendedIsoValue(plugin: PluginContext, volume?: Volu
try {
const absIsoLevel = await getContourLevelEmdb(plugin, ctx, entryId);
RecommendedIsoValue.Provider.set(volume, Volume.IsoValue.absolute(absIsoLevel));
} catch (e) { }
} catch (e) {
console.warn(e);
}
}));
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -18,6 +18,7 @@ import { SetUtils } from '../../mol-util/set';
import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms';
import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
import { SecondaryStructureProvider } from '../../mol-model-props/computed/secondary-structure';
export enum StructureSelectionCategory {
Type = 'Type',
@@ -250,7 +251,12 @@ const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
MS.core.type.bitflags([SecondaryStructureType.Flag.Helix])
])
})
]), { category: StructureSelectionCategory.Structure });
]), {
category: StructureSelectionCategory.Structure,
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
return SecondaryStructureProvider.attach(ctx, structure);
}
});
const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
@@ -260,7 +266,12 @@ const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.uni
MS.core.type.bitflags([SecondaryStructureType.Flag.Beta])
])
})
]), { category: StructureSelectionCategory.Structure });
]), {
category: StructureSelectionCategory.Structure,
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
return SecondaryStructureProvider.attach(ctx, structure);
}
});
const water = StructureSelectionQuery('Water', MS.struct.modifier.union([
MS.struct.generator.atomGroups({

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>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Structure, StructureElement } from '../../mol-model/structure';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { PluginContext } from '../../mol-plugin/context';
import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
import { Substance } from '../../mol-theme/substance';
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
import { Material } from '../../mol-util/material';
type SubstanceEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, substance?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle>>) => Promise<void>
const SubstanceManagerTag = 'substance-controls';
export async function setStructureSubstance(plugin: PluginContext, components: StructureComponentRef[], material: Material | undefined, lociGetter: (structure: Structure) => Promise<StructureElement.Loci | EmptyLoci>, types?: string[]) {
await eachRepr(plugin, components, async (update, repr, substanceCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
const structure = repr.obj!.data.sourceData;
// always use the root structure to get the loci so the substance
// stays applicable as long as the root structure does not change
const loci = await lociGetter(structure.root);
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
const layer = {
bundle: StructureElement.Bundle.fromLoci(loci),
material: material ?? Material(),
clear: !material
};
if (substanceCell) {
const bundleLayers = [...substanceCell.params!.values.layers, layer];
const filtered = getFilteredBundle(bundleLayers, structure);
update.to(substanceCell).update(Substance.toBundle(filtered));
} else {
const filtered = getFilteredBundle([layer], structure);
update.to(repr.transform.ref)
.apply(StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle, Substance.toBundle(filtered), { tags: SubstanceManagerTag });
}
});
}
export async function clearStructureSubstance(plugin: PluginContext, components: StructureComponentRef[], types?: string[]) {
await eachRepr(plugin, components, async (update, repr, substanceCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
if (substanceCell) {
update.delete(substanceCell.transform.ref);
}
});
}
async function eachRepr(plugin: PluginContext, components: StructureComponentRef[], callback: SubstanceEachReprCallback) {
const state = plugin.state.data;
const update = state.build();
for (const c of components) {
for (const r of c.representations) {
const substance = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle, r.cell.transform.ref).withTag(SubstanceManagerTag));
await callback(update, r.cell, substance[0]);
}
}
return update.commit({ doNotUpdateCurrent: true });
}
/** filter substance layers for given structure */
function getFilteredBundle(layers: Substance.BundleLayer[], structure: Structure) {
const substance = Substance.ofBundle(layers, structure.root);
const merged = Substance.merge(substance);
return Substance.filter(merged, structure);
}

View File

@@ -261,7 +261,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
return this.setStateSnapshot(snapshot);
}
} catch (e) {
this.plugin.log.error(`Reading state: ${e}`);
console.error(e);
this.plugin.log.error('Error reading state');
}
}

View File

@@ -30,6 +30,8 @@ import { Clipping } from '../../../mol-theme/clipping';
import { setStructureClipping } from '../../helpers/structure-clipping';
import { setStructureTransparency } from '../../helpers/structure-transparency';
import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
import { setStructureSubstance } from '../../helpers/structure-substance';
import { Material } from '../../../mol-util/material';
export { StructureComponentManager };
@@ -67,22 +69,24 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
await update.commit();
await this.plugin.state.updateBehavior(StructureFocusRepresentation, p => {
p.ignoreHydrogens = !options.showHydrogens;
p.material = options.materialStyle;
});
if (interactionChanged) await this.updateInterationProps();
});
}
private updateReprParams(update: StateBuilder.Root, component: StructureComponentRef) {
const { showHydrogens, visualQuality: quality } = this.state.options;
const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
const ignoreHydrogens = !showHydrogens;
for (const r of component.representations) {
if (r.cell.transform.transformer !== StructureRepresentation3D) continue;
const params = r.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality) {
if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality || params.type.params.material !== material) {
update.to(r.cell).update(old => {
old.type.params.ignoreHydrogens = ignoreHydrogens;
old.type.params.quality = quality;
old.type.params.material = material;
});
}
}
@@ -301,9 +305,9 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
if (components.length === 0) return;
const { showHydrogens, visualQuality: quality } = this.state.options;
const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
const ignoreHydrogens = !showHydrogens;
const typeParams = { ignoreHydrogens, quality };
const typeParams = { ignoreHydrogens, quality, material };
return this.plugin.dataTransaction(async () => {
for (const component of components) {
@@ -338,9 +342,9 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
const xs = structures || this.currentStructures;
if (xs.length === 0) return;
const { showHydrogens, visualQuality: quality } = this.state.options;
const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
const ignoreHydrogens = !showHydrogens;
const typeParams = { ignoreHydrogens, quality };
const typeParams = { ignoreHydrogens, quality, material };
const componentKey = UUID.create22();
for (const s of xs) {
@@ -372,14 +376,19 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
const getLoci = async (s: Structure) => StructureSelection.toLociWithSourceUnits(await params.selection.getSelection(this.plugin, ctx, s));
for (const s of xs) {
if (params.action.name === 'reset') {
await setStructureOverpaint(this.plugin, s.components, -1, getLoci, params.representations);
} else if (params.action.name === 'color') {
if (params.action.name === 'color') {
const p = params.action.params;
await setStructureOverpaint(this.plugin, s.components, p.color, getLoci, params.representations);
} else if (params.action.name === 'resetColor') {
await setStructureOverpaint(this.plugin, s.components, -1, getLoci, params.representations);
} else if (params.action.name === 'transparency') {
const p = params.action.params;
await setStructureTransparency(this.plugin, s.components, p.value, getLoci, params.representations);
} else if (params.action.name === 'material') {
const p = params.action.params;
await setStructureSubstance(this.plugin, s.components, p.material, getLoci, params.representations);
} else if (params.action.name === 'resetMaterial') {
await setStructureSubstance(this.plugin, s.components, void 0, getLoci, params.representations);
} else if (params.action.name === 'clipping') {
const p = params.action.params;
await setStructureClipping(this.plugin, s.components, Clipping.Groups.fromNames(p.excludeGroups), getLoci, params.representations);
@@ -445,6 +454,7 @@ namespace StructureComponentManager {
export const OptionsParams = {
showHydrogens: PD.Boolean(true, { description: 'Toggle display of hydrogen atoms in representations' }),
visualQuality: PD.Select('auto', VisualQualityOptions, { description: 'Control the visual/rendering quality of representations' }),
materialStyle: Material.getParam(),
interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
};
export type Options = PD.Values<typeof OptionsParams>
@@ -477,10 +487,14 @@ namespace StructureComponentManager {
color: PD.Group({
color: PD.Color(ColorNames.blue, { isExpanded: true }),
}, { isFlat: true }),
reset: PD.EmptyGroup({ label: 'Reset Color' }),
resetColor: PD.EmptyGroup({ label: 'Reset Color' }),
transparency: PD.Group({
value: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
}, { isFlat: true }),
material: PD.Group({
material: Material.getParam({ isFlat: true }),
}, { isFlat: true }),
resetMaterial: PD.EmptyGroup({ label: 'Reset Material' }),
clipping: PD.Group({
excludeGroups: PD.MultiSelect([] as Clipping.Groups.Names[], PD.objectToOptions(Clipping.Groups.Names)),
}, { isFlat: true }),

View File

@@ -308,8 +308,9 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
.apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
textColor: Color.fromRgb(255, 255, 255),
borderColor: Color.fromRgb(0, 0, 0),
borderWidth: 0.5,
textSize: 0.33,
borderWidth: 0.3,
offsetZ: 0.75,
customText: `${order++}`
}, { tags: MeasurementOrderLabelTag });
}

View File

@@ -24,7 +24,7 @@ import { unwindStructureAssembly, explodeStructure, spinStructure, SpinStructure
import { Color } from '../../mol-util/color';
import { Overpaint } from '../../mol-theme/overpaint';
import { Transparency } from '../../mol-theme/transparency';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { BaseGeometry, hasColorSmoothingProp } from '../../mol-geo/geometry/base';
import { Script } from '../../mol-script/script';
import { UnitcellParams, UnitcellRepresentation, getUnitcellData } from '../../mol-repr/shape/model/unitcell';
import { DistanceParams, DistanceRepresentation } from '../../mol-repr/shape/loci/distance';
@@ -41,6 +41,8 @@ import { getBoxMesh } from './shape';
import { Shape } from '../../mol-model/shape';
import { Box3D } from '../../mol-math/geometry';
import { PlaneParams, PlaneRepresentation } from '../../mol-repr/shape/loci/plane';
import { Substance } from '../../mol-theme/substance';
import { Material } from '../../mol-util/material';
export { StructureRepresentation3D };
export { ExplodeStructureRepresentation3D };
@@ -50,6 +52,8 @@ export { OverpaintStructureRepresentation3DFromScript };
export { OverpaintStructureRepresentation3DFromBundle };
export { TransparencyStructureRepresentation3DFromScript };
export { TransparencyStructureRepresentation3DFromBundle };
export { SubstanceStructureRepresentation3DFromScript };
export { SubstanceStructureRepresentation3DFromBundle };
export { ClippingStructureRepresentation3DFromScript };
export { ClippingStructureRepresentation3DFromBundle };
export { VolumeRepresentation3D };
@@ -328,25 +332,31 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const overpaint = Overpaint.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { overpaint },
initialState: { overpaint: Overpaint.Empty },
info: structure,
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const oldStructure = b.data.info as Structure;
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldOverpaint = b.data.state.overpaint!;
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.overpaint = newOverpaint;
b.data.repr = a.data.repr;
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
@@ -380,25 +390,31 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const overpaint = Overpaint.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { overpaint },
initialState: { overpaint: Overpaint.Empty },
info: structure,
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const oldStructure = b.data.info as Structure;
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldOverpaint = b.data.state.overpaint!;
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.overpaint = newOverpaint;
b.data.repr = a.data.repr;
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
@@ -429,24 +445,31 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const transparency = Transparency.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { transparency },
initialState: { transparency: Transparency.Empty },
info: structure,
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Transparency (${transparency.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldTransparency = b.data.state.transparency!;
const newTransparency = Transparency.ofScript(newParams.layers, structure);
const newTransparency = Transparency.ofScript(newParams.layers, newStructure);
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.transparency = newTransparency;
b.data.repr = a.data.repr;
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
@@ -478,24 +501,31 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const transparency = Transparency.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { transparency },
initialState: { transparency: Transparency.Empty },
info: structure,
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Transparency (${transparency.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldTransparency = b.data.state.transparency!;
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
const newTransparency = Transparency.ofBundle(newParams.layers, newStructure);
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.transparency = newTransparency;
b.data.repr = a.data.repr;
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
@@ -503,6 +533,121 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
}
});
type SubstanceStructureRepresentation3DFromScript = typeof SubstanceStructureRepresentation3DFromScript
const SubstanceStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn({
name: 'substance-structure-representation-3d-from-script',
display: 'Substance 3D Representation',
from: SO.Molecule.Structure.Representation3D,
to: SO.Molecule.Structure.Representation3DState,
params: () => ({
layers: PD.ObjectList({
script: PD.Script(Script('(sel.atom.all)', 'mol-script')),
material: Material.getParam(),
clear: PD.Boolean(false)
}, e => `${e.clear ? 'Clear' : Material.toString(e.material)}`, {
defaultValue: [{
script: Script('(sel.atom.all)', 'mol-script'),
material: Material({ roughness: 1 }),
clear: false
}]
}),
})
})({
canAutoUpdate() {
return true;
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const substance = Substance.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { substance },
initialState: { substance: Substance.Empty },
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Substance (${substance.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldSubstance = b.data.state.substance!;
const newSubstance = Substance.ofScript(newParams.layers, newStructure);
if (Substance.areEqual(oldSubstance, newSubstance)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.substance = newSubstance;
b.data.repr = a.data.repr;
b.label = `Substance (${newSubstance.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
});
type SubstanceStructureRepresentation3DFromBundle = typeof SubstanceStructureRepresentation3DFromBundle
const SubstanceStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn({
name: 'substance-structure-representation-3d-from-bundle',
display: 'Substance 3D Representation',
from: SO.Molecule.Structure.Representation3D,
to: SO.Molecule.Structure.Representation3DState,
params: () => ({
layers: PD.ObjectList({
bundle: PD.Value<StructureElement.Bundle>(StructureElement.Bundle.Empty),
material: Material.getParam(),
clear: PD.Boolean(false)
}, e => `${e.clear ? 'Clear' : Material.toString(e.material)}`, {
defaultValue: [{
bundle: StructureElement.Bundle.Empty,
material: Material({ roughness: 1 }),
clear: false
}],
isHidden: true
}),
})
})({
canAutoUpdate() {
return true;
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const substance = Substance.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { substance },
initialState: { substance: Substance.Empty },
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Substance (${substance.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldSubstance = b.data.state.substance!;
const newSubstance = Substance.ofBundle(newParams.layers, newStructure);
if (Substance.areEqual(oldSubstance, newSubstance)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.substance = newSubstance;
b.data.repr = a.data.repr;
b.label = `Substance (${newSubstance.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
});
type ClippingStructureRepresentation3DFromScript = typeof ClippingStructureRepresentation3DFromScript
const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn({
name: 'clipping-structure-representation-3d-from-script',

View File

@@ -19,7 +19,7 @@ import { PluginUIComponent } from '../base';
import { ActionMenu } from './action-menu';
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
import { Icon, HelpOutlineSvg, CheckSvg, ClearSvg, BookmarksOutlinedSvg, MoreHorizSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg } from './icons';
import { Icon, HelpOutlineSvg, CheckSvg, ClearSvg, BookmarksOutlinedSvg, MoreHorizSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, TuneSvg } from './icons';
import { legendFor } from './legend';
import { LineGraphComponent } from './line-graph/line-graph-component';
import { Slider, Slider2 } from './slider';
@@ -1079,8 +1079,8 @@ export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiS
}
}
export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>> & { inMapped?: boolean }, { isExpanded: boolean, showHelp: boolean }> {
state = { isExpanded: !!this.props.param.isExpanded, showHelp: false }
export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>> & { inMapped?: boolean }, { isExpanded: boolean, showPresets: boolean, showHelp: boolean }> {
state = { isExpanded: !!this.props.param.isExpanded, showPresets: false, showHelp: false }
change(value: any) {
this.props.onChange({ name: this.props.name, param: this.props.param, value });
@@ -1091,6 +1091,29 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
}
toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
toggleShowPresets = () => this.setState({ showPresets: !this.state.showPresets });
presetItems = memoizeLatest((param: PD.Group<any>) => ActionMenu.createItemsFromSelectOptions(param.presets ?? []));
onSelectPreset: ActionMenu.OnSelect = item => {
this.setState({ showPresets: false });
this.change(item?.value);
}
presets() {
if (!this.props.param.presets) return null;
const label = this.props.param.label || camelCaseToWords(this.props.name);
return <div className='msp-control-group-wrapper'>
<div className='msp-control-group-header'>
<button className='msp-btn msp-form-control msp-btn-block' onClick={this.toggleShowPresets}>
<Icon svg={TuneSvg} />
{label} Presets
</button>
</div>
{this.state.showPresets && <ActionMenu items={this.presetItems(this.props.param)} onSelect={this.onSelectPreset} />}
</div>;
}
pivoted() {
const key = this.props.param.pivot as string;
@@ -1116,6 +1139,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
{ctrl}
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
<div className='msp-control-offset'>
{this.presets()}
<ParameterControls params={filtered} onEnter={this.props.onEnter} values={this.props.value} onChange={this.onChangeParam} isDisabled={this.props.isDisabled} />
</div>
</div>;
@@ -1149,6 +1173,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
</button>
</div>
{this.state.isExpanded && <div className='msp-control-offset'>
{this.presets()}
{controls}
</div>}
</div>;

View File

@@ -1,3 +1,5 @@
@use "sass:math";
.msp-control-row {
position: relative;
height: $row-height;
@@ -184,7 +186,7 @@
z-index: 100000;
background: $default-background;
border-top: 1px solid $default-background;
padding-bottom: $control-spacing / 2;
padding-bottom: math.div($control-spacing, 2);
width: 100%;
// input[type=text] {
@@ -195,8 +197,8 @@
.msp-toggle-color-picker-above {
.msp-color-picker {
top: -2 * 32px - 16px - $control-spacing / 2;
height: 2 * 32px + 16px + $control-spacing / 2;
top: -2 * 32px - 16px - math.div($control-spacing, 2);
height: 2 * 32px + 16px + math.div($control-spacing, 2);
}
}
@@ -208,10 +210,6 @@
}
.msp-control-offset {
// border-left-width: $control-spacing / 2;
// border-left-style: solid;
// border-left-color: color-increase-contrast($default-background, 10%);
// padding-left: 1px;
padding-left: $control-spacing;
}
@@ -228,7 +226,7 @@
// }
.msp-control-group-wrapper {
//border-left-width: $control-spacing / 2;
//border-left-width: math.div($control-spacing, 2);
//border-left-style: solid;
//border-left-color: color-increase-contrast($default-background, 10%);
@@ -240,10 +238,10 @@
.msp-control-group-header {
background: $default-background;
> button, div {
padding-left: 4px; // $control-spacing / 2 !important;
padding-left: 4px; // math.div($control-spacing, 2) !important;
text-align: left;
height: 24px !important; // 2 * $row-height / 3 !important;
line-height: 24px !important; // 2 * $row-height / 3 !important;
height: 24px !important;
line-height: 24px !important;
font-size: 85% !important;
background: $default-background !important;
color: color-lower-contrast($font-color, 15%);
@@ -253,8 +251,8 @@
line-height: 24px !important;
}
> span {
padding-left: $control-spacing / 2;
line-height: 2 * $row-height / 3;
padding-left: math.div($control-spacing, 2);
line-height: math.div(2 * $row-height, 3);
font-size: 70%;
background: $default-background;
color: color-lower-contrast($font-color, 15%);
@@ -267,7 +265,7 @@
.msp-control-group-footer {
background: color-increase-contrast($default-background, 5%);
height: $control-spacing / 2;
height: math.div($control-spacing, 2);
font-size: 1px;
margin-top: 1px;
}
@@ -339,7 +337,7 @@
margin-top: 1px;
> div {
padding: ($control-spacing / 2) $control-spacing;
padding: (math.div($control-spacing, 2)) $control-spacing;
text-align: left;
color: color-lower-contrast($font-color, 15%);
}
@@ -359,7 +357,7 @@
height: $control-spacing * 3;
> span {
padding: $control-spacing / 2;
padding: math.div($control-spacing, 2);
color: white;
font-weight: bold;
background-color: rgba(0, 0, 0, 0.2);
@@ -370,7 +368,7 @@
.msp-table-legend {
> div {
// min-width: 60px;
margin-right: $control-spacing / 2;
margin-right: math.div($control-spacing, 2);
display: inline-flex;
.msp-table-legend-color {
@@ -379,7 +377,7 @@
}
.msp-table-legend-text {
margin: 0 ($control-spacing / 2);
margin: 0 (math.div($control-spacing, 2));
}
}
}

View File

@@ -1,4 +1,6 @@
@use "sass:math";
.msp-toast-container {
position: relative;
// bottom: $control-spacing;
@@ -75,7 +77,7 @@
bottom: 0;
width: 100%;
text-align: right;
padding-right: $control-spacing / 2;
padding-right: math.div($control-spacing, 2);
}
}
}

View File

@@ -157,7 +157,8 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
this.setState({ busy: true });
try {
await this.applyAction();
} catch {
} catch (e) {
console.error(e);
// eat errors because they should be handled elsewhere
} finally {
this.props.onApply?.();

View File

@@ -246,7 +246,8 @@ export class RemoteStateSnapshots extends PluginUIComponent<
if (this._mounted) this.setState({ entries: entries.asImmutable(), isBusy: false });
} catch (e) {
this.plugin.log.error('Fetching Remote Snapshots: ' + e);
console.error(e);
this.plugin.log.error('Error fetching remote snapshots');
if (this._mounted) this.setState({ entries: OrderedMap(), isBusy: false });
}
}
@@ -294,7 +295,9 @@ export class RemoteStateSnapshots extends PluginUIComponent<
try {
await fetch(entry.removeUrl);
} catch { }
} catch (e) {
console.error(e);
}
}
render() {

View File

@@ -401,7 +401,8 @@ function ResidueListSelectionHelper({ modifier, plugin, close }: { modifier: Str
const query = compileIdListSelection(state.identifiers, state.idType);
plugin.managers.structure.selection.fromCompiledQuery(modifier, query, false);
} catch (e) {
plugin.log.error(`Failed to create selection: ${e}`);
console.error(e);
plugin.log.error('Failed to create selection');
}
};

View File

@@ -56,11 +56,10 @@ const SimpleSettingsParams = {
transparent: PD.Boolean(false)
}, { pivot: 'color' }),
lighting: PD.Group({
renderStyle: Canvas3DParams.renderer.params.style,
occlusion: Canvas3DParams.postprocessing.params.occlusion,
outline: Canvas3DParams.postprocessing.params.outline,
fog: Canvas3DParams.cameraFog,
}, { pivot: 'renderStyle' }),
}, { isFlat: true }),
clipping: PD.Group<any>({
...Canvas3DParams.cameraClipping.params,
...(Canvas3DParams.renderer.params.clip as any).params as any
@@ -105,10 +104,9 @@ const SimpleSettingsMapping = ParamMapping({
transparent: canvas.transparentBackground
},
lighting: {
renderStyle: renderer.style,
occlusion: canvas.postprocessing.occlusion,
outline: canvas.postprocessing.outline,
fog: canvas.cameraFog
fog: canvas.cameraFog,
},
clipping: {
...canvas.cameraClipping,
@@ -123,7 +121,6 @@ const SimpleSettingsMapping = ParamMapping({
canvas.camera = s.camera;
canvas.transparentBackground = s.background.transparent;
canvas.renderer.backgroundColor = s.background.color;
canvas.renderer.style = s.lighting.renderStyle;
canvas.postprocessing.occlusion = s.lighting.occlusion;
canvas.postprocessing.outline = s.lighting.outline;
canvas.cameraFog = s.lighting.fog;

View File

@@ -18,6 +18,7 @@ import { SizeTheme } from '../../../../mol-theme/size';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { PluginCommands } from '../../../commands';
import { PluginContext } from '../../../context';
import { Material } from '../../../../mol-util/material';
const StructureFocusRepresentationParams = (plugin: PluginContext) => {
const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
@@ -41,7 +42,8 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
}),
components: PD.MultiSelect(FocusComponents, PD.arrayToOptions(FocusComponents)),
excludeTargetFromSurroundings: PD.Boolean(false, { label: 'Exclude Target', description: 'Exclude the focus "target" from the surroudings component.' }),
ignoreHydrogens: PD.Boolean(false)
ignoreHydrogens: PD.Boolean(false),
material: Material.getParam(),
};
};
@@ -67,7 +69,7 @@ class StructureFocusRepresentationBehavior extends PluginBehavior.WithSubscriber
...this.params.targetParams,
type: {
name: reprParams.type.name,
params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens }
params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens, material: this.params.material }
}
};
}

View File

@@ -199,8 +199,7 @@ export class PluginContext {
const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit, preferWebGl1 });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
}
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
this.canvas3dInit.next(true);

View File

@@ -101,6 +101,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript),
PluginSpec.Action(StateTransforms.Representation.SubstanceStructureRepresentation3DFromScript),
PluginSpec.Action(AssignColorVolume),
PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),

View File

@@ -20,6 +20,7 @@ import { merge } from 'rxjs';
import { PluginContext } from './context';
import { PluginComponent } from '../mol-plugin-state/component';
import { PluginConfig } from './config';
import { StructureComponentManager } from '../mol-plugin-state/manager/structure/component';
export { PluginState };
@@ -64,6 +65,9 @@ class PluginState extends PluginComponent {
canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0,
interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0,
structureFocus: this.plugin.managers.structure.focus.getSnapshot(),
structureComponentManager: p.componentManager ? {
options: this.plugin.managers.structure.component.state.options
} : void 0,
durationInMs: p?.durationInMs
};
}
@@ -71,6 +75,8 @@ class PluginState extends PluginComponent {
async setSnapshot(snapshot: PluginState.Snapshot) {
await this.animation.stop();
// this needs to go 1st since these changes are already baked into the behavior and data state
if (snapshot.structureComponentManager?.options) await this.plugin.managers.structure.component.setOptions(snapshot.structureComponentManager?.options);
if (snapshot.behaviour) await this.plugin.runTask(this.behaviors.setSnapshot(snapshot.behaviour));
if (snapshot.data) await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
if (snapshot.canvas3d?.props) {
@@ -139,6 +145,7 @@ namespace PluginState {
durationInMs: PD.Numeric(1500, { min: 100, max: 15000, step: 100 }, { label: 'Duration in ms' }),
data: PD.Boolean(true),
behavior: PD.Boolean(false),
componentManager: PD.Boolean(true),
animation: PD.Boolean(true),
startAnimation: PD.Boolean(false),
canvas3d: PD.Boolean(true),
@@ -172,6 +179,9 @@ namespace PluginState {
props?: InteractivityManager.Props
},
structureFocus?: StructureFocusSnapshot,
structureComponentManager?: {
options?: StructureComponentManager.Options
},
durationInMs?: number
}

View File

@@ -23,6 +23,9 @@ import { BaseGeometry } from '../mol-geo/geometry/base';
import { Visual } from './visual';
import { CustomProperty } from '../mol-model-props/common/custom-property';
import { Clipping } from '../mol-theme/clipping';
import { SetUtils } from '../mol-util/set';
import { cantorPairing } from '../mol-data/util';
import { Substance } from '../mol-theme/substance';
export type RepresentationProps = { [k: string]: any }
@@ -143,6 +146,7 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
/** Number of addressable groups in all visuals of the representation */
readonly groupCount: number
readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
readonly geometryVersion: number
readonly props: Readonly<PD.Values<P>>
readonly params: Readonly<P>
readonly state: Readonly<S>
@@ -183,6 +187,8 @@ namespace Representation {
overpaint: Overpaint
/** Per group transparency applied to the representation's renderobjects */
transparency: Transparency
/** Per group material applied to the representation's renderobjects */
substance: Substance
/** Bit mask of per group clipping applied to the representation's renderobjects */
clipping: Clipping
/** Controls if the representation's renderobjects are synced automatically with GPU or not */
@@ -193,7 +199,7 @@ namespace Representation {
markerActions: MarkerActions
}
export function createState(): State {
return { visible: true, alphaFactor: 1, pickable: true, colorOnly: false, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, clipping: Clipping.Empty, markerActions: MarkerActions.All };
return { visible: true, alphaFactor: 1, pickable: true, colorOnly: false, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, substance: Substance.Empty, clipping: Clipping.Empty, markerActions: MarkerActions.All };
}
export function updateState(state: State, update: Partial<State>) {
if (update.visible !== undefined) state.visible = update.visible;
@@ -202,6 +208,7 @@ namespace Representation {
if (update.colorOnly !== undefined) state.colorOnly = update.colorOnly;
if (update.overpaint !== undefined) state.overpaint = update.overpaint;
if (update.transparency !== undefined) state.transparency = update.transparency;
if (update.substance !== undefined) state.substance = update.substance;
if (update.clipping !== undefined) state.clipping = update.clipping;
if (update.syncManually !== undefined) state.syncManually = update.syncManually;
if (update.transform !== undefined) Mat4.copy(state.transform, update.transform);
@@ -215,7 +222,7 @@ namespace Representation {
export type Any = Representation<any, any, any>
export const Empty: Any = {
label: '', groupCount: 0, renderObjects: [], props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
label: '', groupCount: 0, renderObjects: [], geometryVersion: -1, props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
createOrUpdate: () => Task.constant('', undefined),
setState: () => {},
setTheme: () => {},
@@ -226,9 +233,32 @@ namespace Representation {
export type Def<D, P extends PD.Params = {}, S extends State = State> = { [k: string]: RepresentationFactory<D, P, S> }
export class GeometryState {
private curr = new Set<number>();
private next = new Set<number>();
private _version = -1;
get version() {
return this._version;
}
add(id: number, version: number) {
this.next.add(cantorPairing(id, version));
}
snapshot() {
if (!SetUtils.areEqual(this.curr, this.next)) {
this._version += 1;
}
[this.curr, this.next] = [this.next, this.curr];
this.next.clear();
}
}
export function createMulti<D, P extends PD.Params = {}, S extends State = State>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>, stateBuilder: StateBuilder<S>, reprDefs: Def<D, P>): Representation<D, P, S> {
let version = 0;
const updated = new Subject<number>();
const geometryState = new GeometryState();
const currentState = stateBuilder.create();
let currentTheme = Theme.createEmpty();
@@ -271,6 +301,7 @@ namespace Representation {
}
return renderObjects;
},
get geometryVersion() { return geometryState.version; },
get props() { return currentProps; },
get params() { return currentParams; },
createOrUpdate: (props: Partial<P> = {}, data?: D) => {
@@ -288,7 +319,9 @@ namespace Representation {
if (!visuals || visuals.includes(reprMap[i])) {
await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime);
}
geometryState.add(i, reprList[i].geometryVersion);
}
geometryState.snapshot();
updated.next(version++);
});
},
@@ -314,7 +347,7 @@ namespace Representation {
setState: (state: Partial<S>) => {
stateBuilder.update(currentState, state);
for (let i = 0, il = reprList.length; i < il; ++i) {
reprList[i].setState(currentState);
reprList[i].setState(state); // only set the new (partial) state
}
},
setTheme: (theme: Theme) => {
@@ -334,6 +367,7 @@ namespace Representation {
export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation<GraphicsRenderObject, BaseGeometry.Params> {
let version = 0;
const updated = new Subject<number>();
const geometryState = new GeometryState();
const currentState = Representation.createState();
const currentTheme = Theme.createEmpty();
@@ -345,6 +379,7 @@ namespace Representation {
updated,
get groupCount() { return renderObject.values.uGroupCount.ref.value; },
get renderObjects() { return [renderObject]; },
get geometryVersion() { return geometryState.version; },
get props() { return currentProps; },
get params() { return currentParams; },
createOrUpdate: (props: Partial<PD.Values<BaseGeometry.Params>> = {}) => {
@@ -353,6 +388,8 @@ namespace Representation {
return Task.create(`Updating '${label}' representation`, async runtime => {
// TODO
geometryState.add(0, renderObject.id);
geometryState.snapshot();
updated.next(version++);
});
},
@@ -377,6 +414,9 @@ namespace Representation {
if (state.transparency !== undefined) {
// TODO
}
if (state.substance !== undefined) {
// TODO
}
if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform);
Representation.updateState(currentState, state);

View File

@@ -44,6 +44,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
const renderObjects: GraphicsRenderObject<G['kind']>[] = [];
let _renderObject: GraphicsRenderObject<G['kind']> | undefined;
let _shape: Shape<G>;
let geometryVersion = -1;
const _theme = Theme.createEmpty();
let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P); // TODO avoid casting
let currentParams: P;
@@ -157,6 +158,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
}
currentProps = newProps;
if (updateState.createGeometry || updateState.createNew) {
geometryVersion += 1;
}
// increment version
updated.next(version++);
});
@@ -178,6 +182,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
get state() { return _state; },
get theme() { return _theme; },
renderObjects,
get geometryVersion() { return geometryVersion; },
updated,
createOrUpdate,
getLoci(pickingId?: PickingId) {
@@ -211,6 +216,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
if (state.transparency !== undefined) {
Visual.setTransparency(_renderObject, state.transparency, lociApply, true);
}
if (state.substance !== undefined) {
Visual.setSubstance(_renderObject, state.substance, lociApply, true);
}
if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform);
}

View File

@@ -7,7 +7,7 @@
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ComplexVisual, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
import { RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Structure, StructureElement, Bond } from '../../mol-model/structure';
import { Subject } from 'rxjs';
import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
@@ -21,11 +21,13 @@ import { StructureParams } from './params';
import { Clipping } from '../../mol-theme/clipping';
import { Transparency } from '../../mol-theme/transparency';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Substance } from '../../mol-theme/substance';
export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => ComplexVisual<P>): StructureRepresentation<P> {
let version = 0;
const { webgl } = ctx;
const updated = new Subject<number>();
const geometryState = new Representation.GeometryState();
const materialId = getNextMaterialId();
const renderObjects: GraphicsRenderObject[] = [];
const _state = StructureRepresentationStateBuilder.create();
@@ -59,9 +61,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
if (newVisual) setState(_state); // current state for new visual
// update list of renderObjects
renderObjects.length = 0;
if (visual && visual.renderObject) renderObjects.push(visual.renderObject);
if (visual && visual.renderObject) {
renderObjects.push(visual.renderObject);
geometryState.add(visual.renderObject.id, visual.geometryVersion);
}
geometryState.snapshot();
// increment version
updated.next(version++);
version += 1;
updated.next(version);
});
}
@@ -100,12 +107,17 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
if (state.overpaint !== undefined && visual) {
// Remap loci from equivalent structure to the current structure
const remappedOverpaint = Overpaint.remap(state.overpaint, _structure);
visual.setOverpaint(remappedOverpaint);
visual.setOverpaint(remappedOverpaint, webgl);
}
if (state.transparency !== undefined && visual) {
// Remap loci from equivalent structure to the current structure
const remappedTransparency = Transparency.remap(state.transparency, _structure);
visual.setTransparency(remappedTransparency);
visual.setTransparency(remappedTransparency, webgl);
}
if (state.substance !== undefined && visual) {
// Remap loci from equivalent structure to the current structure
const remappedSubstance = Substance.remap(state.substance, _structure);
visual.setSubstance(remappedSubstance, webgl);
}
if (state.clipping !== undefined && visual) {
// Remap loci from equivalent structure to the current structure
@@ -138,6 +150,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
get params() { return _params; },
get state() { return _state; },
get theme() { return _theme; },
get geometryVersion() { return geometryState.version; },
renderObjects,
updated,
createOrUpdate,

View File

@@ -36,6 +36,7 @@ import { Clipping } from '../../mol-theme/clipping';
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { isPromiseLike } from '../../mol-util/type-helpers';
import { Substance } from '../../mol-theme/substance';
export interface ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
@@ -80,6 +81,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
let currentStructure: Structure;
let geometry: G;
let geometryVersion = -1;
let locationIt: LocationIterator;
let positionIt: LocationIterator;
@@ -187,7 +189,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
currentProps = newProps;
currentTheme = newTheme;
currentStructure = newStructure;
if (newGeometry) geometry = newGeometry;
if (newGeometry) {
geometry = newGeometry;
geometryVersion += 1;
}
}
function lociIsSuperset(loci: Loci) {
@@ -216,6 +221,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
return {
get groupCount() { return locationIt ? locationIt.count : 0; },
get renderObject() { return locationIt && locationIt.count ? renderObject : undefined; },
get geometryVersion() { return geometryVersion; },
createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, structure?: Structure) {
prepareUpdate(theme, props, structure || currentStructure);
if (updateState.createGeometry) {
@@ -253,11 +259,17 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
Visual.setTransform(renderObject, matrix, instanceMatrices);
},
setOverpaint(overpaint: Overpaint) {
Visual.setOverpaint(renderObject, overpaint, lociApply, true);
setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) {
const smoothing = { geometry, props: currentProps, webgl };
Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing);
},
setTransparency(transparency: Transparency) {
Visual.setTransparency(renderObject, transparency, lociApply, true);
setTransparency(transparency: Transparency, webgl?: WebGLContext) {
const smoothing = { geometry, props: currentProps, webgl };
Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
},
setSubstance(substance: Substance, webgl?: WebGLContext) {
const smoothing = { geometry, props: currentProps, webgl };
Visual.setSubstance(renderObject, substance, lociApply, true, smoothing);
},
setClipping(clipping: Clipping) {
Visual.setClipping(renderObject, clipping, lociApply, true);

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