Compare commits

..

70 Commits

Author SHA1 Message Date
Alexander Rose
1db0ada684 debugging 2022-08-20 16:50:39 -07:00
Alexander Rose
e474e9b090 3.14.0 2022-08-20 16:43:16 -07:00
Alexander Rose
837f9a6c74 changelog 2022-08-20 16:37:46 -07:00
Alexander Rose
c357aed7bb schema updates 2022-08-20 16:36:48 -07:00
Alexander Rose
59ffddfd8d update packages 2022-08-20 16:33:17 -07:00
Alexander Rose
fb3accaa36 Merge pull request #528 from molstar/safari-surf-fix
wrap gl_VertexID in int()
2022-08-20 15:45:14 -07:00
Alexander Rose
b3e79544ad Merge branch 'master' into safari-surf-fix 2022-08-20 15:44:30 -07:00
Alexander Rose
2ee0f3bf97 Merge pull request #515 from molstar/background-pass
Background pass
2022-08-20 15:41:32 -07:00
Alexander Rose
a56b5edc4e cleanup 2022-08-20 15:32:01 -07:00
Alexander Rose
f2d71b6551 Merge branch 'master' into background-pass 2022-08-20 15:25:20 -07:00
Alexander Rose
ef560ddc03 Merge pull request #529 from molstar/webgl-state
Webgl state
2022-08-20 15:22:04 -07:00
Alexander Rose
2e30ffe1bc Merge branch 'master' into webgl-state 2022-08-20 15:21:54 -07:00
Alexander Rose
325b5e9297 Merge pull request #527 from molstar/custom-prop-fix
fix CustomElementProperty coloring
2022-08-20 15:21:01 -07:00
Alexander Rose
ae9e04b8d4 reduce number of webgl state changes
- add viewport and scissor to state object
- add hasOpaque to scene object
2022-08-20 12:04:51 -07:00
Alexander Rose
ab0010122b handle renderable rendering edge cases
- fix text background rendering for opaque text
- fix helper scenes not shown when rendering directly to draw target
2022-08-20 12:04:04 -07:00
Alexander Rose
08d736ecdc image loading error handling and other tweaks 2022-08-20 11:54:51 -07:00
Alexander Rose
9c362c8ffd Merge branch 'master' of https://github.com/molstar/molstar into background-pass 2022-08-20 11:07:57 -07:00
Alexander Rose
62c8778560 Merge pull request #513 from molstar/inter-bonds-props
expose inter-bonds props & improve performance
2022-08-20 11:06:04 -07:00
Alexander Rose
2fe0665e12 simplify box3d functions 2022-08-20 10:57:40 -07:00
Alexander Rose
14a957f517 Merge branch 'master' of https://github.com/molstar/molstar into inter-bonds-props 2022-08-20 10:56:14 -07:00
Alexander Rose
087010d0a1 Merge pull request #525 from molstar/pairBonds-maxDistance
set some IndexPairBonds maxDistance to Infinity
2022-08-20 10:54:12 -07:00
Alexander Rose
f92657310a Merge branch 'master' into pairBonds-maxDistance 2022-08-20 10:54:03 -07:00
Alexander Rose
19e91400b5 fix CustomElementProperty coloring
- can't check data availabilty in isApplicable because it is obtained on demand
2022-08-20 10:14:21 -07:00
Alexander Rose
7885fb7b4f wrap gl_VertexID in int()
-fix GPU surfaces rendering in Safari with WebGL2
2022-08-20 10:12:51 -07:00
Alexander Rose
331bec11ee cleanup comment 2022-08-20 10:08:47 -07:00
dsehnal
f219cd6c8b prefer webgl1 in Safari 16 2022-08-18 17:39:18 +02:00
Alexander Rose
e697624064 prefer WebGL1 for more Safari versions
-avoid broken GPU surfaces rendering
2022-08-17 22:08:17 -07:00
Alexander Rose
92ffdeb5bf don't include glycam names in default saccharides 2022-08-17 21:57:55 -07:00
Alexander Rose
ddefe7e542 package updates 2022-08-17 21:50:54 -07:00
Alexander Rose
fb4019c041 changelog 2022-08-17 21:39:14 -07:00
Alexander Rose
46026e047e set some IndexPairBonds maxDistance to Infinity
- for MOL/SDF and MOL2 (without symmetry) models
- avoid filtering by element-based rules
2022-08-17 21:38:06 -07:00
Alexander Rose
0dfad5a757 imporve labels of skybox params 2022-08-15 20:55:45 -07:00
Alexander Rose
a0495f8aae fix SSAO renderable initialization 2022-08-15 20:55:27 -07:00
Alexander Rose
1610f05b83 Merge branch 'master' of https://github.com/molstar/molstar into inter-bonds-props 2022-08-14 16:28:21 -07:00
Alexander Rose
8202b75cda Merge branch 'master' of https://github.com/molstar/molstar into background-pass 2022-08-14 16:26:47 -07:00
Alexander Rose
4904bae5a6 background pass improvements
- add PluginConfig.Background.Styles
- file support, asset management
- opacity, saturation, lightness controls for skybox/image
- coverage controls for image/gradient
- add backgrounds extension with examples
- image handling for build/watch (webpack, cpx)
2022-08-14 16:24:28 -07:00
Alexander Rose
04c06db02c Merge pull request #519 from MadCatX/gzip_files
Allow download of Gzipped files
2022-08-14 14:52:10 -07:00
Michal Malý
a96f94b676 Allow download of Gzipped files 2022-08-14 17:11:35 +02:00
Alexander Rose
ebdfc694c2 Merge pull request #520 from MadCatX/clean_up_pyramids
Change the lookup logic of NtC steps from residues
2022-08-13 11:19:30 -07:00
Michal Malý
7f29340797 Change the lookup logic of NtC steps from residues 2022-08-12 15:29:26 +02:00
Alexander Rose
113d0b5141 add background pass
- skybox, image, horizontal/radial gradient
2022-08-07 13:27:06 -07:00
Alexander Rose
163285b0a9 cleanup 2022-08-07 13:20:52 -07:00
Alexander Rose
9f1cf5377a add sceneRadiusFactor param 2022-08-07 13:17:52 -07:00
Alexander Rose
c37636215b expose fov camera param 2022-08-07 13:14:57 -07:00
Alexander Rose
1f77b19ced changelog 2022-08-06 13:45:29 -07:00
Alexander Rose
9853ebf02f Merge pull request #507 from MadCatX/add_pyramid_labels
Add labels for Confal pyramids
2022-08-06 13:42:02 -07:00
Alexander Rose
6e13aa0bc9 expose inter-bonds props & improve performance 2022-08-06 13:31:10 -07:00
Michal Malý
1b7f0e0f1e Add example mmCIF to allow testing of Confal pyramids 2022-08-04 10:09:50 +02:00
Michal Malý
18cb3360b5 Update changelog 2022-08-04 09:56:28 +02:00
Michal Malý
6fec598b96 Add labels for Confal pyramids 2022-08-01 14:46:35 +02:00
David Sehnal
40096ecdfb Merge pull request #502 from giagitom/master 2022-07-27 10:05:35 +02:00
giagitom
43061b80b8 Deliver defaultAttribs to Passes constructor 2022-07-26 19:24:31 +02:00
Alexander Rose
aa3d657d42 3.13.0 2022-07-24 17:11:08 -07:00
Alexander Rose
b0ef385769 changelog 2022-07-24 17:05:47 -07:00
Alexander Rose
dcf24e6292 Merge pull request #496 from JonStargaryen/master
Download CCD from Configurable URL
2022-07-24 17:04:28 -07:00
Alexander Rose
2fdd77737c Merge pull request #499 from molstar/immediate-isolevel
enable immediateUpdate for iso level
2022-07-24 17:02:55 -07:00
Alexander Rose
31c98ef1ba package updates 2022-07-23 13:40:23 -07:00
Alexander Rose
ceeec2c13a enable immediateUpdate for iso level 2022-07-23 13:36:15 -07:00
Alexander Rose
cc82e0cff8 Merge pull request #498 from molstar/varying-group
Varying group
2022-07-23 13:19:10 -07:00
Alexander Rose
29fc6c59e9 support constant group in gpu mc 2022-07-23 13:18:16 -07:00
Alexander Rose
aa931fab7b add dVaryingGroup to avoid flat qualifier more 2022-07-23 13:06:35 -07:00
Alexander Rose
8e2585a5c0 add material annotation support for textures 2022-07-23 11:26:34 -07:00
Alexander Rose
c115047f74 handle principal axes of points in a plane 2022-07-23 11:06:01 -07:00
Alexander Rose
0ac58cb137 changelog 2022-07-23 11:02:01 -07:00
Alexander Rose
492e0977c3 Merge pull request #494 from giagitom/master
only update camera state if manualReset is off
2022-07-23 10:57:36 -07:00
JonStargaryen
e8a09e81f3 fix short arg names 2022-07-21 14:02:57 -07:00
JonStargaryen
4fcc2c6208 download CCD from configurable URL 2022-07-21 09:50:01 -07:00
giagitom
e3523dc5fe only update camera state if manualReset is off 2022-07-20 18:04:03 +02:00
dsehnal
acf6c31a36 3.12.1 2022-07-20 15:43:33 +02:00
dsehnal
339b2e696c PluginBehavior dispose logic 2022-07-20 15:40:30 +02:00
102 changed files with 4594 additions and 1580 deletions

View File

@@ -6,6 +6,51 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.14.0] - 2022-08-20
- Expose inter-bonds compute params in structure
- Improve performance of inter/intra-bonds compute
- Fix defaultAttribs handling in Canvas3DContext.fromCanvas
- Confal pyramids extension improvements
- Add custom labels to Confal pyramids
- Improve naming of some internal types in Confal pyramids extension coordinate
- Add example mmCIF file with categories necessary to display Confal pyramids
- Change the lookup logic of NtC steps from residues
- Add support for download of gzipped files
- Don't filter IndexPairBonds by element-based rules in MOL/SDF and MOL2 (without symmetry) models
- Fix Glycam Saccharide Names used by default
- Fix GPU surfaces rendering in Safari with WebGL2
- Add ``fov`` (Field of View) Canvas3D parameter
- Add ``sceneRadiusFactor`` Canvas3D parameter
- Add background pass (skybox, image, horizontal/radial gradient)
- Set simple-settings presets via ``PluginConfig.Background.Styles``
- Example presets in new backgrounds extension
- Load skybox/image from URL or File (saved in session)
- Opacity, saturation, lightness controls for skybox/image
- Coverage (viewport or canvas) controls for image/gradient
- [Breaking] ``AssetManager`` needs to be passed to various graphics related classes
- Fix SSAO renderable initialization
- Reduce number of webgl state changes
- Add ``viewport`` and ``scissor`` to state object
- Add ``hasOpaque`` to scene object
- Handle edge cases where some renderables would not get (correctly) rendered
- Fix text background rendering for opaque text
- Fix helper scenes not shown when rendering directly to draw target
- Fix ``CustomElementProperty`` coloring not working
## [v3.13.0] - 2022-07-24
- Fix: only update camera state if manualReset is off (#494)
- Improve handling principal axes of points in a plane
- Add 'material' annotation support for textures
- More effort to avoid using ``flat`` qualifier in shaders: add ``dVaryingGroup``
- Enable ``immediateUpdate`` for iso level in isosurface and volume streaming controls
- Add support to download CCD from configurable URL
## [v3.12.1] - 2022-07-20
- Fix plugin behavior dispose logic to correctly unsubscribe observables.
## [v3.12.0] - 2022-07-17
- Add ``colorMarker`` option to Renderer. This disables the highlight and select marker at a shader level for faster rendering of large scenes in some cases.

View File

@@ -126,7 +126,7 @@ and navigate to `build/viewer`
**GraphQL schemas**
node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml
node node_modules/@graphql-codegen/cli/cjs/bin -c src/extensions/rcsb/graphql/codegen.yml
### Other scripts
**Create chem comp bond table**
@@ -152,7 +152,7 @@ Or
node lib/commonjs/cli/cif2bcif
E.g.
node lib/commonjs/cli/cif2bcif src.cif out.bcif.gz
node lib/commonjs/cli/cif2bcif src.bcif.gz out.cif

File diff suppressed because it is too large Load Diff

1987
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.12.0",
"version": "3.14.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -20,7 +20,7 @@
"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/",
"build-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/",
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
"watch": "concurrently -c \"green,green,gray,gray\" --names \"tsc,srv,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-servers\" \"npm:watch-extra\" \"npm:watch-webpack\"",
@@ -28,7 +28,7 @@
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
"watch-tsc": "tsc --watch --incremental",
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
"watch-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --stats minimal",
"watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js",
"watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js",
@@ -75,7 +75,9 @@
"node_modules",
"lib"
],
"testURL": "http://localhost/",
"testEnvironmentOptions": {
"url": "http://localhost/"
},
"testRegex": "\\.spec\\.ts$"
},
"author": "Mol* Contributors",
@@ -91,30 +93,30 @@
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.2.0",
"@graphql-codegen/cli": "^2.8.1",
"@graphql-codegen/time": "^3.2.0",
"@graphql-codegen/typescript": "^2.7.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.0",
"@graphql-codegen/typescript-graphql-request": "^4.5.1",
"@graphql-codegen/typescript-operations": "^2.5.1",
"@graphql-codegen/add": "^3.2.1",
"@graphql-codegen/cli": "^2.11.6",
"@graphql-codegen/time": "^3.2.1",
"@graphql-codegen/typescript": "^2.7.3",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
"@graphql-codegen/typescript-graphql-request": "^4.5.3",
"@graphql-codegen/typescript-operations": "^2.5.3",
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.1",
"@types/jest": "^28.1.6",
"@types/react": "^18.0.15",
"@types/jest": "^28.1.7",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"benchmark": "^2.1.4",
"concurrently": "^7.2.2",
"concurrently": "^7.3.0",
"cpx2": "^4.2.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"eslint": "^8.19.0",
"eslint": "^8.22.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.1.0",
"graphql": "^16.5.0",
"graphql": "^16.6.0",
"http-server": "^14.1.1",
"jest": "^28.1.3",
"mini-css-extract-plugin": "^2.6.1",
@@ -122,14 +124,14 @@
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.53.0",
"sass": "^1.54.5",
"sass-loader": "^13.0.2",
"simple-git": "^3.10.0",
"simple-git": "^3.12.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^28.0.6",
"ts-jest": "^28.0.8",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"dependencies": {
@@ -137,7 +139,7 @@
"@types/benchmark": "^2.1.1",
"@types/compression": "1.7.2",
"@types/express": "^4.17.13",
"@types/node": "^16.11.45",
"@types/node": "^16.11.51",
"@types/node-fetch": "^2.6.2",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
@@ -150,7 +152,7 @@
"immutable": "^4.1.0",
"node-fetch": "^2.6.7",
"rxjs": "^7.5.6",
"swagger-ui-dist": "^4.12.0",
"swagger-ui-dist": "^4.14.0",
"tslib": "^2.4.0",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"

View File

@@ -46,6 +46,7 @@ import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
import { Backgrounds } from '../../extensions/backgrounds';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
@@ -55,6 +56,7 @@ const CustomFormats = [
];
const Extensions = {
'backgrounds': PluginSpec.Behavior(Backgrounds),
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),

View File

@@ -15,7 +15,7 @@ const writeFile = util.promisify(fs.writeFile);
import { DatabaseCollection } from '../../mol-data/db';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
import { ensureDataAvailable, readCCD } from './util';
import { DefaultDataOptions, ensureDataAvailable, readCCD } from './util';
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
const ionNames: string[] = [];
@@ -44,8 +44,8 @@ export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").re
writeFile(filePath, output);
}
async function run(out: string, forceDownload = false) {
await ensureDataAvailable(forceDownload);
async function run(out: string, options = DefaultDataOptions) {
await ensureDataAvailable(options);
const ccd = await readCCD();
const ionNames = extractIonNames(ccd);
if (!fs.existsSync(path.dirname(out))) {
@@ -65,10 +65,15 @@ parser.add_argument('--forceDownload', '-f', {
action: 'store_true',
help: 'Force download of CCD and PVCD.'
});
parser.add_argument('--ccdUrl', '-c', {
help: 'Fetch the CCD from a custom URL. This forces download of the CCD.',
required: false
});
interface Args {
out: string,
forceDownload?: boolean,
ccdUrl?: string
}
const args: Args = parser.parse_args();
run(args.out, args.forceDownload);
run(args.out, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl });

View File

@@ -14,7 +14,7 @@ const writeFile = util.promisify(fs.writeFile);
import { DatabaseCollection } from '../../mol-data/db';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
import { ensureDataAvailable, readCCD } from './util';
import { DefaultDataOptions, ensureDataAvailable, readCCD } from './util';
function extractSaccharideNames(ccd: DatabaseCollection<CCD_Schema>) {
const saccharideNames: string[] = [];
@@ -47,8 +47,8 @@ export const SaccharideNames = new Set(${JSON.stringify(ionNames).replace(/"/g,
writeFile(filePath, output);
}
async function run(out: string, forceDownload = false) {
await ensureDataAvailable(forceDownload);
async function run(out: string, options = DefaultDataOptions) {
await ensureDataAvailable(options);
const ccd = await readCCD();
const saccharideNames = extractSaccharideNames(ccd);
if (!fs.existsSync(path.dirname(out))) {
@@ -68,10 +68,15 @@ parser.add_argument('--forceDownload', '-f', {
action: 'store_true',
help: 'Force download of CCD and PVCD.'
});
parser.add_argument('--ccdUrl', '-c', {
help: 'Fetch the CCD from a custom URL. This forces download of the CCD.',
required: false
});
interface Args {
out: string,
forceDownload?: boolean,
ccdUrl?: string
}
const args: Args = parser.parse_args();
run(args.out, args.forceDownload);
run(args.out, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl });

View File

@@ -18,7 +18,7 @@ import { SetUtils } from '../../mol-util/set';
import { DefaultMap } from '../../mol-util/map';
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
import { ccd_chemCompAtom_schema } from '../../mol-io/reader/cif/schema/ccd-extras';
import { ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
import { DefaultDataOptions, ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
type CCB = Table<CCD_Schema['chem_comp_bond']>
type CCA = Table<CCD_Schema['chem_comp_atom']>
@@ -239,8 +239,8 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>, pvcd: DatabaseCollecti
);
}
async function run(out: string, binary = false, forceDownload = false, ccaOut?: string) {
await ensureDataAvailable(forceDownload);
async function run(out: string, binary = false, options = DefaultDataOptions, ccaOut?: string) {
await ensureDataAvailable(options);
const ccd = await readCCD();
const pvcd = await readPVCD();
@@ -283,12 +283,22 @@ parser.add_argument('--ccaOut', '-a', {
help: 'Optional generated file output path for chem_comp_atom data.',
required: false
});
parser.add_argument('--ccdUrl', '-c', {
help: 'Fetch the CCD from a custom URL. This forces download of the CCD.',
required: false
});
parser.add_argument('--pvcdUrl', '-p', {
help: 'Fetch the PVCD from a custom URL. This forces download of the PVCD.',
required: false
});
interface Args {
out: string,
forceDownload?: boolean,
binary?: boolean,
ccaOut?: string
ccaOut?: string,
ccdUrl?: string,
pvcdUrl?: string
}
const args: Args = parser.parse_args();
run(args.out, args.binary, args.forceDownload, args.ccaOut);
run(args.out, args.binary, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl, pvcdUrl: args.pvcdUrl }, args.ccaOut);

View File

@@ -35,9 +35,9 @@ export async function ensureAvailable(path: string, url: string, forceDownload =
}
}
export async function ensureDataAvailable(forceDownload = false) {
await ensureAvailable(CCD_PATH, CCD_URL, forceDownload);
await ensureAvailable(PVCD_PATH, PVCD_URL, forceDownload);
export async function ensureDataAvailable(options: DataOptions) {
await ensureAvailable(CCD_PATH, options.ccdUrl || CCD_URL, !!options.ccdUrl || options.forceDownload);
await ensureAvailable(PVCD_PATH, options.pvcdUrl || PVCD_URL, !!options.pvcdUrl || options.forceDownload);
}
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
@@ -68,6 +68,16 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>,
return encoder.getData();
}
export type DataOptions = {
ccdUrl?: string,
pvcdUrl?: string,
forceDownload?: boolean
}
export const DefaultDataOptions: DataOptions = {
forceDownload: false
};
const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data');
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { PluginConfig } from '../../mol-plugin/config';
import { Color } from '../../mol-util/color/color';
// from https://visualsonline.cancer.gov/details.cfm?imageid=2304, public domain
import image_cells from './images/cells.jpg';
// created with http://alexcpeterson.com/spacescape/
import face_nebula_nx from './skyboxes/nebula/nebula_left2.jpg';
import face_nebula_ny from './skyboxes/nebula/nebula_bottom4.jpg';
import face_nebula_nz from './skyboxes/nebula/nebula_back6.jpg';
import face_nebula_px from './skyboxes/nebula/nebula_right1.jpg';
import face_nebula_py from './skyboxes/nebula/nebula_top3.jpg';
import face_nebula_pz from './skyboxes/nebula/nebula_front5.jpg';
export const Backgrounds = PluginBehavior.create<{ }>({
name: 'extension-backgrounds',
category: 'misc',
display: {
name: 'Backgrounds'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.config.set(PluginConfig.Background.Styles, [
[{
variant: {
name: 'radialGradient',
params: {
centerColor: Color(0xFFFFFF),
edgeColor: Color(0x808080),
ratio: 0.2,
coverage: 'viewport',
}
}
}, 'Light Radial Gradient'],
[{
variant: {
name: 'image',
params: {
source: {
name: 'url',
params: image_cells
},
lightness: 0,
saturation: 0,
opacity: 1,
coverage: 'viewport',
}
}
}, 'Normal Cells Image'],
[{
variant: {
name: 'skybox',
params: {
faces: {
name: 'urls',
params: {
nx: face_nebula_nx,
ny: face_nebula_ny,
nz: face_nebula_nz,
px: face_nebula_px,
py: face_nebula_py,
pz: face_nebula_pz,
}
},
lightness: 0,
saturation: 0,
opacity: 1,
}
}
}, 'Purple Nebula Skybox'],
]);
}
update() {
return false;
}
unregister() {
this.ctx.config.set(PluginConfig.Background.Styles, []);
}
},
params: () => ({ })
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

10
src/extensions/backgrounds/typings.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
declare module '*.jpg' {
const value: string;
export = value;
}

View File

@@ -8,7 +8,7 @@
import { ConfalPyramidsColorThemeProvider } from './color';
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsRepresentationProvider } from './representation';
import { Loci } from '../../../mol-model/loci';
import { ConfalPyramidsTypes } from './types';
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../../mol-state';
@@ -56,21 +56,10 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean,
description: 'Schematic depiction of conformer class and confal value.',
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showToolTip: boolean }> {
private provider = ConfalPyramidsProvider;
private labelConfalPyramids = {
label: (loci: Loci): string | undefined => {
if (!this.params.showToolTip) return void 0;
/* TODO: Implement this */
return void 0;
}
};
register(): void {
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
this.ctx.managers.lociLabels.addProvider(this.labelConfalPyramids);
this.ctx.representation.structure.themes.colorThemeRegistry.add(ConfalPyramidsColorThemeProvider);
this.ctx.representation.structure.registry.add(ConfalPyramidsRepresentationProvider);
@@ -88,7 +77,6 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean,
unregister() {
this.ctx.customModelProperties.unregister(ConfalPyramidsProvider.descriptor.name);
this.ctx.managers.lociLabels.removeProvider(this.labelConfalPyramids);
this.ctx.representation.structure.registry.remove(ConfalPyramidsRepresentationProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(ConfalPyramidsColorThemeProvider);
@@ -101,3 +89,13 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean,
showToolTip: PD.Boolean(true)
})
});
export function confalPyramidLabel(halfPyramid: ConfalPyramidsTypes.HalfPyramid) {
const { step } = halfPyramid;
return `
<b>${step.auth_asym_id_1}</b> |
<b>${step.label_comp_id_1} ${step.auth_seq_id_1}${step.PDB_ins_code_1}${step.label_alt_id_1.length > 0 ? ` (alt ${step.label_alt_id_1})` : ''}
${step.label_comp_id_2} ${step.auth_seq_id_2}${step.PDB_ins_code_2}${step.label_alt_id_2.length > 0 ? ` (alt ${step.label_alt_id_2})` : ''} </b><br />
<i>NtC:</i> ${step.NtC} | <i>Confal score:</i> ${step.confal_score} | <i>RMSD:</i> ${step.rmsd.toFixed(2)}
`;
}

View File

@@ -247,8 +247,8 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
function color(location: Location, isSecondary: boolean): Color {
if (CPT.isLocation(location)) {
const { pyramid, isLower } = location.data;
const key = pyramid.NtC + `_${isLower ? 'Lwr' : 'Upr'}` as keyof PyramidsColors;
const { step, isLower } = location.data;
const key = step.NtC + `_${isLower ? 'Lwr' : 'Upr'}` as keyof PyramidsColors;
return colorMap[key] ?? ErrorColor;
}

View File

@@ -16,7 +16,7 @@ import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
export type ConfalPyramids = PropertyWrapper<CPT.PyramidsData | undefined >;
export type ConfalPyramids = PropertyWrapper<CPT.Steps | undefined>;
export namespace ConfalPyramids {
export const Schema = {
@@ -105,34 +105,42 @@ export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramids
type StepsSummaryTable = Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step_summary>;
function createPyramidsFromCif(model: Model,
steps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>,
stepsSummary: StepsSummaryTable): CPT.PyramidsData {
const pyramids = new Array<CPT.Pyramid>();
const names = new Map<string, number>();
const locations = new Array<CPT.Location>();
let hasMultipleModels = false;
function createPyramidsFromCif(
model: Model,
cifSteps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>,
stepsSummary: StepsSummaryTable
): CPT.Steps {
const steps = new Array<CPT.Step>();
const mapping = new Array<CPT.MappedChains>();
const {
id, PDB_model_number, name,
auth_asym_id_1, auth_seq_id_1, label_comp_id_1, label_alt_id_1, PDB_ins_code_1,
auth_asym_id_2, auth_seq_id_2, label_comp_id_2, label_alt_id_2, PDB_ins_code_2,
_rowCount } = steps;
_rowCount
} = cifSteps;
if (_rowCount !== stepsSummary._rowCount) throw new Error('Inconsistent mmCIF data');
for (let i = 0; i < _rowCount; i++) {
const model_num = PDB_model_number.value(i);
if (model_num !== model.modelNum)
hasMultipleModels = true;
const {
NtC,
confal_score,
rmsd
} = getSummaryData(id.value(i), i, stepsSummary);
const modelNum = PDB_model_number.value(i);
const chainId = auth_asym_id_1.value(i);
const seqId = auth_seq_id_1.value(i);
const modelIdx = modelNum - 1;
const { _NtC, _confal_score } = getNtCAndConfalScore(id.value(i), i, stepsSummary);
if (mapping.length <= modelIdx || !mapping[modelIdx])
mapping[modelIdx] = new Map<string, CPT.MappedResidues>();
const pyramid = {
PDB_model_number: model_num,
const step = {
PDB_model_number: modelNum,
name: name.value(i),
auth_asym_id_1: auth_asym_id_1.value(i),
auth_seq_id_1: auth_seq_id_1.value(i),
auth_asym_id_1: chainId,
auth_seq_id_1: seqId,
label_comp_id_1: label_comp_id_1.value(i),
label_alt_id_1: label_alt_id_1.value(i),
PDB_ins_code_1: PDB_ins_code_1.value(i),
@@ -141,30 +149,41 @@ function createPyramidsFromCif(model: Model,
label_comp_id_2: label_comp_id_2.value(i),
label_alt_id_2: label_alt_id_2.value(i),
PDB_ins_code_2: PDB_ins_code_2.value(i),
confal_score: _confal_score,
NtC: _NtC
confal_score,
NtC,
rmsd,
};
pyramids.push(pyramid);
names.set(pyramid.name, pyramids.length - 1);
steps.push(step);
locations.push(CPT.Location(pyramid, false));
locations.push(CPT.Location(pyramid, true));
const mappedChains = mapping[modelIdx];
const residuesOnChain = mappedChains.get(chainId) ?? new Map<number, number[]>();
const stepsForResidue = residuesOnChain.get(seqId) ?? [];
stepsForResidue.push(steps.length - 1);
residuesOnChain.set(seqId, stepsForResidue);
mappedChains.set(chainId, residuesOnChain);
mapping[modelIdx] = mappedChains;
}
return { pyramids, names, locations, hasMultipleModels };
return { steps, mapping };
}
function getNtCAndConfalScore(id: number, i: number, stepsSummary: StepsSummaryTable) {
const { step_id, confal_score, assigned_NtC } = stepsSummary;
function getSummaryData(id: number, i: number, stepsSummary: StepsSummaryTable) {
const {
step_id,
confal_score,
assigned_NtC,
cartesian_rmsd_closest_NtC_representative,
} = stepsSummary;
// Assume that step_ids in ntc_step_summary are in the same order as steps in ntc_step
for (let j = i; j < stepsSummary._rowCount; j++) {
if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) };
if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) };
}
// Safety net for cases where the previous assumption is not met
for (let j = 0; j < i; j++) {
if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) };
if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) };
}
throw new Error('Inconsistent mmCIF data');
}

View File

@@ -6,7 +6,7 @@
*/
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsUtil } from './util';
import { ConfalPyramidsIterator } from './util';
import { ConfalPyramidsTypes as CPT } from './types';
import { Interval } from '../../../mol-data/int';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
@@ -16,14 +16,14 @@ import { PrimitiveBuilder } from '../../../mol-geo/primitive/primitive';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Structure, StructureProperties, Unit } from '../../../mol-model/structure';
import { Structure, Unit } from '../../../mol-model/structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { VisualContext } from '../../../mol-repr/visual';
import { getAltResidueLociFromId, StructureGroup } from '../../../mol-repr/structure/visual/util/common';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
import { NullLocation } from '../../../mol-model/location';
@@ -32,6 +32,12 @@ const t = Mat4.identity();
const w = Vec3.zero();
const mp = Vec3.zero();
const posO3 = Vec3();
const posP = Vec3();
const posOP1 = Vec3();
const posOP2 = Vec3();
const posO5 = Vec3();
function calcMidpoint(mp: Vec3, v: Vec3, w: Vec3) {
Vec3.sub(mp, v, w);
Vec3.scale(mp, mp, 0.5);
@@ -53,64 +59,76 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
const { structure, group } = structureGroup;
const instanceCount = group.units.length;
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) {
return LocationIterator(0, 1, 1, () => NullLocation);
}
const data = ConfalPyramidsProvider.get(structure.model)?.value?.data;
if (!data) return LocationIterator(0, 1, 1, () => NullLocation);
const { locations } = prop.data;
const halfPyramidsCount = data.steps.length * 2;
const getLocation = (groupIndex: number, instanceIndex: number) => {
if (locations.length <= groupIndex) return NullLocation;
return locations[groupIndex];
if (halfPyramidsCount <= groupIndex) return NullLocation;
const idx = Math.floor(groupIndex / 2); // Map groupIndex to a step, see createConfalPyramidsMesh() for full explanation
return CPT.Location(data.steps[idx], groupIndex % 2 === 1);
};
return LocationIterator(locations.length, instanceCount, 1, getLocation);
return LocationIterator(halfPyramidsCount, instanceCount, 1, getLocation);
}
function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) return Mesh.createEmpty(mesh);
const data = ConfalPyramidsProvider.get(structure.model)?.value?.data;
if (!data) return Mesh.createEmpty(mesh);
const { pyramids } = prop.data;
if (pyramids.length === 0) return Mesh.createEmpty(mesh);
const { steps, mapping } = data;
if (steps.length === 0) return Mesh.createEmpty(mesh);
const vertexCount = (6 * steps.length) / mapping.length;
const mb = MeshBuilder.createState(512, 512, mesh);
const mb = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh);
const handler = (pyramid: CPT.Pyramid, first: ConfalPyramidsUtil.FirstResidueAtoms, second: ConfalPyramidsUtil.SecondResidueAtoms, firsLocIndex: number, secondLocIndex: number) => {
if (firsLocIndex === -1 || secondLocIndex === -1)
throw new Error('Invalid location index');
const it = new ConfalPyramidsIterator(structure, unit);
while (it.hasNext) {
const allPoints = it.move();
const scale = (pyramid.confal_score - 20.0) / 100.0;
const O3 = first.O3.pos;
const OP1 = second.OP1.pos; const OP2 = second.OP2.pos; const O5 = second.O5.pos; const P = second.P.pos;
for (const points of allPoints) {
const { O3, P, OP1, OP2, O5, confalScore } = points;
const scale = (confalScore - 20.0) / 100.0;
// Steps can be drawn in a different order than they are stored.
// To make sure that we can get from the drawn pyramid back to the step in represents,
// we need to use an appropriate groupId. The stepIdx passed from the iterator
// is an index into the array of all steps in the structure.
// Since a step is drawn as two "half-pyramids" we need two ids to map to a single step.
// To do that, we just multiply the index by 2. idx*2 marks the "upper" half-pyramid,
// (idx*2)+1 the "lower" half-pyramid.
const groupIdx = points.stepIdx * 2;
shiftVertex(O3, P, scale);
shiftVertex(OP1, P, scale);
shiftVertex(OP2, P, scale);
shiftVertex(O5, P, scale);
calcMidpoint(mp, O3, O5);
unit.conformation.invariantPosition(O3, posO3);
unit.conformation.invariantPosition(P, posP);
unit.conformation.invariantPosition(OP1, posOP1);
unit.conformation.invariantPosition(OP2, posOP2);
unit.conformation.invariantPosition(O5, posO5);
mb.currentGroup = firsLocIndex;
let pb = PrimitiveBuilder(3);
/* Upper part (for first residue in step) */
pb.add(O3, OP1, OP2);
pb.add(O3, mp, OP1);
pb.add(O3, OP2, mp);
MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
shiftVertex(posO3, posP, scale);
shiftVertex(posOP1, posP, scale);
shiftVertex(posOP2, posP, scale);
shiftVertex(posO5, posP, scale);
calcMidpoint(mp, posO3, posO5);
/* Lower part (for second residue in step */
mb.currentGroup = secondLocIndex;
pb = PrimitiveBuilder(3);
pb.add(mp, O5, OP1);
pb.add(mp, OP2, O5);
pb.add(O5, OP2, OP1);
MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
};
mb.currentGroup = groupIdx;
let pb = PrimitiveBuilder(3);
/* Upper part (for first residue in step) */
pb.add(posO3, posOP1, posOP2);
pb.add(posO3, mp, posOP1);
pb.add(posO3, posOP2, mp);
MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
const walker = new ConfalPyramidsUtil.UnitWalker(structure, unit, handler);
walker.walk();
/* Lower part (for second residue in step) */
mb.currentGroup = groupIdx + 1;
pb = PrimitiveBuilder(3);
pb.add(mp, posO5, posOP1);
pb.add(mp, posOP2, posO5);
pb.add(posO5, posOP2, posOP1);
MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
}
}
return MeshBuilder.getMesh(mb);
}
@@ -124,16 +142,17 @@ function getConfalPyramidLoci(pickingId: PickingId, structureGroup: StructureGro
const unit = structureGroup.group.units[instanceId];
if (!Unit.isAtomic(unit)) return EmptyLoci;
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) return EmptyLoci;
const data = ConfalPyramidsProvider.get(structure.model)?.value?.data;
if (!data) return EmptyLoci;
const { locations } = prop.data;
const halfPyramidsCount = data.steps.length * 2;
if (locations.length <= groupId) return EmptyLoci;
const altId = StructureProperties.atom.label_alt_id(CPT.toElementLocation(locations[groupId]));
const rI = unit.residueIndex[locations[groupId].element.element];
if (halfPyramidsCount <= groupId) return EmptyLoci;
return getAltResidueLociFromId(structure, unit, rI, altId);
const idx = Math.floor(groupId / 2); // Map groupIndex to a step, see createConfalPyramidsMesh() for full explanation
const step = data.steps[idx];
return CPT.Loci({ step, isLower: groupId % 2 === 1 }, [{}]);
}
function eachConfalPyramid(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {

View File

@@ -6,10 +6,13 @@
*/
import { DataLocation } from '../../../mol-model/location';
import { ElementIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure';
import { DataLoci } from '../../../mol-model/loci';
import { confalPyramidLabel } from './behavior';
export namespace ConfalPyramidsTypes {
export type Pyramid = {
export const DataTag = 'dnatco-confal-half-pyramid';
export type Step = {
PDB_model_number: number,
name: string,
auth_asym_id_1: string,
@@ -23,38 +26,40 @@ export namespace ConfalPyramidsTypes {
label_alt_id_2: string,
PDB_ins_code_2: string,
confal_score: number,
NtC: string
NtC: string,
rmsd: number,
}
export interface PyramidsData {
pyramids: Array<Pyramid>,
names: Map<string, number>,
locations: Array<Location>,
hasMultipleModels: boolean
export type MappedChains = Map<string, MappedResidues>;
export type MappedResidues = Map<number, number[]>;
export interface Steps {
steps: Array<Step>,
mapping: MappedChains[],
}
export interface LocationData {
readonly pyramid: Pyramid
readonly isLower: boolean;
export interface HalfPyramid {
step: Step,
isLower: boolean,
}
export interface Element {
structure: Structure;
unit: Unit.Atomic;
element: ElementIndex;
}
export interface Location extends DataLocation<HalfPyramid, {}> {}
export interface Location extends DataLocation<LocationData, Element> {}
export function Location(pyramid: Pyramid, isLower: boolean, structure?: Structure, unit?: Unit.Atomic, element?: ElementIndex) {
return DataLocation('pyramid', { pyramid, isLower }, { structure: structure as any, unit: unit as any, element: element as any });
export function Location(step: Step, isLower: boolean) {
return DataLocation(DataTag, { step, isLower }, {});
}
export function isLocation(x: any): x is Location {
return !!x && x.kind === 'data-location' && x.tag === 'pyramid';
return !!x && x.kind === 'data-location' && x.tag === DataTag;
}
export function toElementLocation(location: Location) {
return StructureElement.Location.create(location.element.structure, location.element.unit, location.element.element);
export interface Loci extends DataLoci<HalfPyramid, {}> {}
export function Loci(data: HalfPyramid, elements: ReadonlyArray<{}>): Loci {
return DataLoci(DataTag, data, elements, undefined, () => confalPyramidLabel(data));
}
export function isLoci(x: any): x is Loci {
return !!x && x.kind === 'data-loci' && x.tag === DataTag;
}
}

View File

@@ -8,288 +8,120 @@
import { ConfalPyramidsProvider } from './property';
import { ConfalPyramidsTypes as CPT } from './types';
import { Segmentation } from '../../../mol-data/int';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
export namespace ConfalPyramidsUtil {
type Residue = Segmentation.Segment<ResidueIndex>;
type Residue = Segmentation.Segment<ResidueIndex>;
export type AtomInfo = {
pos: Vec3,
index: ElementIndex,
fakeAltId: string,
};
export type Pyramid = {
O3: ElementIndex,
P: ElementIndex,
OP1: ElementIndex,
OP2: ElementIndex,
O5: ElementIndex,
confalScore: number,
stepIdx: number,
};
export type FirstResidueAtoms = {
O3: AtomInfo,
};
const EmptyStepIndices = new Array<number>();
export type SecondResidueAtoms = {
OP1: AtomInfo,
OP2: AtomInfo,
O5: AtomInfo,
P: AtomInfo,
};
function copyResidue(r?: Residue) {
return r ? { index: r.index, start: r.start, end: r.end } : void 0;
}
type ResidueInfo = {
PDB_model_num: number,
asym_id: string,
auth_asym_id: string,
seq_id: number,
auth_seq_id: number,
comp_id: string,
alt_id: string,
ins_code: string,
};
function getAtomIndex(loc: StructureElement.Location, residue: Residue, names: string[], altId: string): ElementIndex {
for (let eI = residue.start; eI < residue.end; eI++) {
loc.element = loc.unit.elements[eI];
const elName = StructureProperties.atom.label_atom_id(loc);
const elAltId = StructureProperties.atom.label_alt_id(loc);
export type Handler = (pyramid: CPT.Pyramid, first: FirstResidueAtoms, second: SecondResidueAtoms, firstLocIndex: number, secondLocIndex: number) => void;
function residueInfoFromLocation(loc: StructureElement.Location): ResidueInfo {
return {
PDB_model_num: StructureProperties.unit.model_num(loc),
asym_id: StructureProperties.chain.label_asym_id(loc),
auth_asym_id: StructureProperties.chain.auth_asym_id(loc),
seq_id: StructureProperties.residue.label_seq_id(loc),
auth_seq_id: StructureProperties.residue.auth_seq_id(loc),
comp_id: StructureProperties.atom.label_comp_id(loc),
alt_id: StructureProperties.atom.label_alt_id(loc),
ins_code: StructureProperties.residue.pdbx_PDB_ins_code(loc)
};
if (names.includes(elName) && (elAltId === altId || elAltId.length === 0))
return loc.element;
}
export function hasMultipleModels(unit: Unit.Atomic): boolean {
return -1 as ElementIndex;
}
function getPyramid(loc: StructureElement.Location, one: Residue, two: Residue, altIdOne: string, altIdTwo: string, confalScore: number, stepIdx: number): Pyramid {
const O3 = getAtomIndex(loc, one, ['O3\'', 'O3*'], altIdOne);
const P = getAtomIndex(loc, two, ['P'], altIdTwo);
const OP1 = getAtomIndex(loc, two, ['OP1'], altIdTwo);
const OP2 = getAtomIndex(loc, two, ['OP2'], altIdTwo);
const O5 = getAtomIndex(loc, two, ['O5\'', 'O5*'], altIdTwo);
return { O3, P, OP1, OP2, O5, confalScore, stepIdx };
}
export class ConfalPyramidsIterator {
private chainIt: Segmentation.SegmentIterator<ChainIndex>;
private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
private residueOne?: Residue;
private residueTwo: Residue;
private data?: CPT.Steps;
private loc: StructureElement.Location;
private getStepIndices(r: Residue) {
this.loc.element = this.loc.unit.elements[r.start];
const modelIdx = StructureProperties.unit.model_num(this.loc) - 1;
const chainId = StructureProperties.chain.auth_asym_id(this.loc);
const seqId = StructureProperties.residue.auth_seq_id(this.loc);
const chains = this.data!.mapping[modelIdx];
if (!chains) return EmptyStepIndices;
const residues = chains.get(chainId);
if (!residues) return EmptyStepIndices;
return residues.get(seqId) ?? EmptyStepIndices;
}
private moveStep() {
this.residueOne = copyResidue(this.residueTwo);
this.residueTwo = copyResidue(this.residueIt.move())!;
return this.toPyramids(this.residueOne!, this.residueTwo);
}
private toPyramids(one: Residue, two: Residue) {
const indices = this.getStepIndices(one);
const points = [];
for (const idx of indices) {
const step = this.data!.steps[idx];
points.push(getPyramid(this.loc, one, two, step.label_alt_id_1, step.label_alt_id_2, step.confal_score, idx));
}
return points;
}
constructor(structure: Structure, unit: Unit) {
this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
const prop = ConfalPyramidsProvider.get(unit.model).value;
if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
return prop.data.hasMultipleModels;
this.data = prop?.data;
if (this.chainIt.hasNext) {
this.residueIt.setSegment(this.chainIt.move());
if (this.residueIt.hasNext)
this.residueTwo = this.residueIt.move();
}
this.loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
}
function getPossibleAltIds(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
const possibleAltIds: string[] = [];
const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
for (let rI = residue.start; rI <= residue.end - 1; rI++) {
loc.element = unit.elements[rI];
const altId = StructureProperties.atom.label_alt_id(loc);
if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId);
}
return possibleAltIds;
get hasNext() {
if (!this.data)
return false;
return this.residueIt.hasNext
? true
: this.chainIt.hasNext;
}
class Utility {
protected getPyramidByName(name: string): { pyramid: CPT.Pyramid | undefined, index: number } {
const index = this.data.names.get(name);
if (index === undefined) return { pyramid: undefined, index: -1 };
return { pyramid: this.data.pyramids[index], index };
move() {
if (this.residueIt.hasNext) {
return this.moveStep();
} else {
this.residueIt.setSegment(this.chainIt.move());
return this.moveStep();
}
protected stepToName(entry_id: string, modelNum: number, locFirst: StructureElement.Location, locSecond: StructureElement.Location, fakeAltId_1: string, fakeAltId_2: string) {
const first = residueInfoFromLocation(locFirst);
const second = residueInfoFromLocation(locSecond);
const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';
return `${entry_id}${model_id}_${first.auth_asym_id}_${first.comp_id}${alt_id_1}_${first.auth_seq_id}${ins_code_1}_${second.comp_id}${alt_id_2}_${second.auth_seq_id}${ins_code_2}`;
}
constructor(unit: Unit.Atomic) {
const prop = ConfalPyramidsProvider.get(unit.model).value;
if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
this.data = prop.data;
this.hasMultipleModels = hasMultipleModels(unit);
this.entryId = unit.model.entryId.toLowerCase();
this.modelNum = unit.model.modelNum;
}
protected readonly data: CPT.PyramidsData;
protected readonly hasMultipleModels: boolean;
protected readonly entryId: string;
protected readonly modelNum: number;
}
export class UnitWalker extends Utility {
private getAtomIndices(names: string[], residue: Residue): ElementIndex[] {
const indices: ElementIndex[] = [];
const loc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
for (let rI = residue.start; rI <= residue.end - 1; rI++) {
loc.element = this.unit.elements[rI];
const thisName = StructureProperties.atom.label_atom_id(loc);
if (names.includes(thisName)) indices.push(loc.element);
}
if (indices.length === 0) {
let namesStr = '';
for (const n of names)
namesStr += `${n} `;
throw new Error(`Element [${namesStr}] not found on residue ${residue.index}`);
}
return indices;
}
private getAtomPositions(indices: ElementIndex[]): Vec3[] {
const pos = this.unit.conformation.invariantPosition;
const positions: Vec3[] = [];
for (const eI of indices) {
const v = Vec3.zero();
pos(eI, v);
positions.push(v);
}
return positions;
}
private handleStep(firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[]) {
const modelNum = this.hasMultipleModels ? this.modelNum : -1;
let ok = false;
const firstLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
const secondLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
for (let i = 0; i < firstAtoms.length; i++) {
const first = firstAtoms[i];
for (let j = 0; j < secondAtoms.length; j++) {
const second = secondAtoms[j];
firstLoc.element = first.O3.index;
secondLoc.element = second.OP1.index;
const name = this.stepToName(this.entryId, modelNum, firstLoc, secondLoc, first.O3.fakeAltId, second.OP1.fakeAltId);
const { pyramid, index } = this.getPyramidByName(name);
if (pyramid !== undefined) {
const setLoc = (loc: CPT.Location, eI: ElementIndex) => {
loc.element.structure = this.structure;
loc.element.unit = this.unit;
loc.element.element = eI;
};
const locIndex = index * 2;
setLoc(this.data.locations[locIndex], firstLoc.element);
setLoc(this.data.locations[locIndex + 1], secondLoc.element);
this.handler(pyramid, first, second, locIndex, locIndex + 1);
ok = true;
}
}
}
if (!ok) throw new Error('Bogus step');
}
private processFirstResidue(residue: Residue, possibleAltIds: string[]) {
const indO3 = this.getAtomIndices(['O3\'', 'O3*'], residue);
const posO3 = this.getAtomPositions(indO3);
const altPos: FirstResidueAtoms[] = [
{ O3: { pos: posO3[0], index: indO3[0], fakeAltId: '' } }
];
for (let i = 1; i < indO3.length; i++) {
altPos.push({ O3: { pos: posO3[i], index: indO3[i], fakeAltId: '' } });
}
if (altPos.length === 1 && possibleAltIds.length > 1) {
/* We have some alternate positions on the residue but O3 does not have any - fake them */
altPos[0].O3.fakeAltId = possibleAltIds[0];
for (let i = 1; i < possibleAltIds.length; i++)
altPos.push({ O3: { pos: posO3[0], index: indO3[0], fakeAltId: possibleAltIds[i] } });
}
return altPos;
}
private processSecondResidue(residue: Residue, possibleAltIds: string[]) {
const indOP1 = this.getAtomIndices(['OP1'], residue);
const indOP2 = this.getAtomIndices(['OP2'], residue);
const indO5 = this.getAtomIndices(['O5\'', 'O5*'], residue);
const indP = this.getAtomIndices(['P'], residue);
const posOP1 = this.getAtomPositions(indOP1);
const posOP2 = this.getAtomPositions(indOP2);
const posO5 = this.getAtomPositions(indO5);
const posP = this.getAtomPositions(indP);
const infoOP1: AtomInfo[] = [];
/* We use OP1 as "pivotal" atom. There is no specific reason
* to pick OP1, it is as good a choice as any other atom
*/
if (indOP1.length === 1 && possibleAltIds.length > 1) {
/* No altIds on OP1, fake them */
for (const altId of possibleAltIds)
infoOP1.push({ pos: posOP1[0], index: indOP1[0], fakeAltId: altId });
} else {
for (let i = 0; i < indOP1.length; i++)
infoOP1.push({ pos: posOP1[i], index: indOP1[i], fakeAltId: '' });
}
const mkInfo = (i: number, indices: ElementIndex[], positions: Vec3[], altId: string) => {
if (i >= indices.length) {
const last = indices.length - 1;
return { pos: positions[last], index: indices[last], fakeAltId: altId };
}
return { pos: positions[i], index: indices[i], fakeAltId: altId };
};
const altPos: SecondResidueAtoms[] = [];
for (let i = 0; i < infoOP1.length; i++) {
const altId = infoOP1[i].fakeAltId;
const OP2 = mkInfo(i, indOP2, posOP2, altId);
const O5 = mkInfo(i, indO5, posO5, altId);
const P = mkInfo(i, indP, posP, altId);
altPos.push({ OP1: infoOP1[i], OP2, O5, P });
}
return altPos;
}
private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } {
const firstPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds);
residue = this.residueIt.move();
const secondPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds);
return { firstAtoms, secondAtoms };
}
walk() {
while (this.chainIt.hasNext) {
this.residueIt.setSegment(this.chainIt.move());
let residue = this.residueIt.move();
while (this.residueIt.hasNext) {
try {
const { firstAtoms, secondAtoms } = this.step(residue);
this.handleStep(firstAtoms, secondAtoms);
} catch (error) {
/* Skip and move along */
residue = this.residueIt.move();
}
}
}
}
constructor(private structure: Structure, private unit: Unit.Atomic, private handler: Handler) {
super(unit);
this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
}
private chainIt: Segmentation.SegmentIterator<ChainIndex>;
private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
}
}

View File

@@ -69,6 +69,7 @@ export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin:
const dt = durationMs / N;
await ctx.update({ message: 'Rendering...', isIndeterminate: false, current: 0, max: N + 1 });
await params.pass.updateBackground();
await plugin.managers.animation.play(params.animation.definition, params.animation.params);
stoppedAnimation = false;

View File

@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 2022-06-26T14:02:35-07:00
// Generated on 2022-08-20T16:36:05-07:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -13,11 +13,8 @@ export type Scalars = {
Boolean: boolean;
Int: number;
Float: number;
/** Built-in scalar representing an instant in time */
Date: any;
/** Built-in scalar for dynamic values */
ObjectScalar: any;
/** Use SPQR's SchemaPrinter to remove this from SDL */
UNREPRESENTABLE: any;
};

View File

@@ -40,6 +40,8 @@ import { Passes } from './passes/passes';
import { shallowEqual } from '../mol-util';
import { MarkingParams } from './passes/marking';
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item';
import { degToRad, radToDeg } from '../mol-math/misc';
import { AssetManager } from '../mol-util/assets';
export const Canvas3DParams = {
camera: PD.Group({
@@ -49,6 +51,7 @@ export const Canvas3DParams = {
on: PD.Group(StereoCameraParams),
off: PD.Group({})
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
fov: PD.Numeric(45, { min: 10, max: 130, step: 1 }, { label: 'Field of View' }),
manualReset: PD.Boolean(false, { isHidden: true }),
}, { pivot: 'mode' }),
cameraFog: PD.MappedStatic('on', {
@@ -78,6 +81,7 @@ export const Canvas3DParams = {
}),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
transparentBackground: PD.Boolean(false),
multiSample: PD.Group(MultiSampleParams),
@@ -106,6 +110,7 @@ interface Canvas3DContext {
readonly attribs: Readonly<Canvas3DContext.Attribs>
readonly contextLost: BehaviorSubject<now.Timestamp>
readonly contextRestored: BehaviorSubject<now.Timestamp>
readonly assetManager: AssetManager
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
@@ -124,7 +129,7 @@ namespace Canvas3DContext {
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
export function fromCanvas(canvas: HTMLCanvasElement, assetManager: AssetManager, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const gl = getGLContext(canvas, {
@@ -139,7 +144,7 @@ namespace Canvas3DContext {
const input = InputObserver.fromElement(canvas, { pixelScale, preventGestures: true });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
const passes = new Passes(webgl, assetManager, a);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
@@ -192,6 +197,7 @@ namespace Canvas3DContext {
attribs: a,
contextLost,
contextRestored: webgl.contextRestored,
assetManager,
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
input.dispose();
@@ -278,7 +284,7 @@ namespace Canvas3D {
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
export function create({ webgl, input, passes, attribs, assetManager }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
@@ -299,11 +305,16 @@ namespace Canvas3D {
const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended);
function getSceneRadius() {
return scene.boundingSphere.radius * p.sceneRadiusFactor;
}
const camera = new Camera({
position: Vec3.create(0, 0, 100),
mode: p.camera.mode,
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
clipFar: p.cameraClipping.far
clipFar: p.cameraClipping.far,
fov: degToRad(p.camera.fov),
}, { x, y, width, height }, { pixelScale: attribs.pixelScale });
const stereoCamera = new StereoCamera(camera, p.camera.stereo.params);
@@ -315,6 +326,10 @@ namespace Canvas3D {
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, p.interaction);
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
passes.draw.postprocessing.background.update(camera, p.postprocessing.background, changed => {
if (changed) requestDraw();
});
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
@@ -523,7 +538,7 @@ namespace Canvas3D {
const focus = camera.getFocus(center, radius);
const next = typeof nextCameraResetSnapshot === 'function' ? nextCameraResetSnapshot(scene, camera) : nextCameraResetSnapshot;
const snapshot = next ? { ...focus, ...next } : focus;
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
camera.setState({ ...snapshot, radiusMax: getSceneRadius() }, duration);
}
nextCameraResetDuration = void 0;
@@ -574,7 +589,7 @@ namespace Canvas3D {
}
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0);
if (!p.camera.manualReset) camera.setState({ radiusMax: getSceneRadius() }, 0);
reprCount.next(reprRenderObjects.size);
if (isDebugMode) consoleStats();
@@ -650,7 +665,7 @@ namespace Canvas3D {
function getProps(): Canvas3DProps {
const radius = scene.boundingSphere.radius > 0
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
? 100 - Math.round((camera.transition.target.radius / getSceneRadius()) * 100)
: 0;
return {
@@ -658,6 +673,7 @@ namespace Canvas3D {
mode: camera.state.mode,
helper: { ...helper.camera.props },
stereo: { ...p.camera.stereo },
fov: Math.round(radToDeg(camera.state.fov)),
manualReset: !!p.camera.manualReset
},
cameraFog: camera.state.fog > 0
@@ -665,6 +681,7 @@ namespace Canvas3D {
: { name: 'off' as const, params: {} },
cameraClipping: { far: camera.state.clipFar, radius },
cameraResetDurationMs: p.cameraResetDurationMs,
sceneRadiusFactor: p.sceneRadiusFactor,
transparentBackground: p.transparentBackground,
viewport: p.viewport,
@@ -767,10 +784,19 @@ namespace Canvas3D {
? produce(getProps(), properties as any)
: properties;
if (props.sceneRadiusFactor !== undefined) {
p.sceneRadiusFactor = props.sceneRadiusFactor;
camera.setState({ radiusMax: getSceneRadius() }, 0);
}
const cameraState: Partial<Camera.Snapshot> = Object.create(null);
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
cameraState.mode = props.camera.mode;
}
const oldFov = Math.round(radToDeg(camera.state.fov));
if (props.camera && props.camera.fov !== undefined && props.camera.fov !== oldFov) {
cameraState.fov = degToRad(props.camera.fov);
}
if (props.cameraFog !== undefined && props.cameraFog.params) {
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0;
if (newFog !== camera.state.fog) cameraState.fog = newFog;
@@ -780,7 +806,7 @@ namespace Canvas3D {
cameraState.clipFar = props.cameraClipping.far;
}
if (props.cameraClipping.radius !== undefined) {
const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius);
const radius = (getSceneRadius() / 100) * (100 - props.cameraClipping.radius);
if (radius > 0 && radius !== cameraState.radius) {
// if radius = 0, NaNs happen
cameraState.radius = Math.max(radius, 0.01);
@@ -805,6 +831,12 @@ namespace Canvas3D {
}
}
if (props.postprocessing?.background) {
Object.assign(p.postprocessing.background, props.postprocessing.background);
passes.draw.postprocessing.background.update(camera, p.postprocessing.background, changed => {
if (changed && !doNotRequestDraw) requestDraw();
});
}
if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing);
if (props.marking) Object.assign(p.marking, props.marking);
if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
@@ -823,7 +855,7 @@ namespace Canvas3D {
}
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
},
getRenderObjects(): GraphicsRenderObject[] {
const renderObjects: GraphicsRenderObject[] = [];

View File

@@ -0,0 +1,461 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { QuadPositions, } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { AttributeSpec, DefineSpec, TextureSpec, UniformSpec, Values, ValueSpec } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { background_frag } from '../../mol-gl/shader/background.frag';
import { background_vert } from '../../mol-gl/shader/background.vert';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createNullTexture, CubeFaces, Texture } from '../../mol-gl/webgl/texture';
import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4';
import { ValueCell } from '../../mol-util/value-cell';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { isTimingMode } from '../../mol-util/debug';
import { Camera, ICamera } from '../camera';
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { Vec2 } from '../../mol-math/linear-algebra/3d/vec2';
import { Color } from '../../mol-util/color';
import { Asset, AssetManager } from '../../mol-util/assets';
import { Vec4 } from '../../mol-math/linear-algebra/3d/vec4';
const SharedParams = {
opacity: PD.Numeric(1, { min: 0.0, max: 1.0, step: 0.01 }),
saturation: PD.Numeric(0, { min: -1, max: 1, step: 0.01 }),
lightness: PD.Numeric(0, { min: -1, max: 1, step: 0.01 }),
};
const SkyboxParams = {
faces: PD.MappedStatic('urls', {
urls: PD.Group({
nx: PD.Text('', { label: 'Negative X / Left' }),
ny: PD.Text('', { label: 'Negative Y / Bottom' }),
nz: PD.Text('', { label: 'Negative Z / Back' }),
px: PD.Text('', { label: 'Positive X / Right' }),
py: PD.Text('', { label: 'Positive Y / Top' }),
pz: PD.Text('', { label: 'Positive Z / Front' }),
}, { isExpanded: true, label: 'URLs' }),
files: PD.Group({
nx: PD.File({ label: 'Negative X / Left', accept: 'image/*' }),
ny: PD.File({ label: 'Negative Y / Bottom', accept: 'image/*' }),
nz: PD.File({ label: 'Negative Z / Back', accept: 'image/*' }),
px: PD.File({ label: 'Positive X / Right', accept: 'image/*' }),
py: PD.File({ label: 'Positive Y / Top', accept: 'image/*' }),
pz: PD.File({ label: 'Positive Z / Front', accept: 'image/*' }),
}, { isExpanded: true, label: 'Files' }),
}),
...SharedParams,
};
type SkyboxProps = PD.Values<typeof SkyboxParams>
const ImageParams = {
source: PD.MappedStatic('url', {
url: PD.Text(''),
file: PD.File({ accept: 'image/*' }),
}),
...SharedParams,
coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])),
};
type ImageProps = PD.Values<typeof ImageParams>
const HorizontalGradientParams = {
topColor: PD.Color(Color(0xDDDDDD)),
bottomColor: PD.Color(Color(0xEEEEEE)),
ratio: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])),
};
const RadialGradientParams = {
centerColor: PD.Color(Color(0xDDDDDD)),
edgeColor: PD.Color(Color(0xEEEEEE)),
ratio: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])),
};
export const BackgroundParams = {
variant: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
skybox: PD.Group(SkyboxParams, { isExpanded: true }),
image: PD.Group(ImageParams, { isExpanded: true }),
horizontalGradient: PD.Group(HorizontalGradientParams, { isExpanded: true }),
radialGradient: PD.Group(RadialGradientParams, { isExpanded: true }),
}, { label: 'Environment' }),
};
export type BackgroundProps = PD.Values<typeof BackgroundParams>
export class BackgroundPass {
private renderable: BackgroundRenderable;
private skybox: {
texture: Texture
props: SkyboxProps
assets: Asset[]
loaded: boolean
} | undefined;
private image: {
texture: Texture
props: ImageProps
asset: Asset
loaded: boolean
} | undefined;
private readonly camera = new Camera();
private readonly target = Vec3();
private readonly position = Vec3();
private readonly dir = Vec3();
readonly texture: Texture;
constructor(private readonly webgl: WebGLContext, private readonly assetManager: AssetManager, width: number, height: number) {
this.renderable = getBackgroundRenderable(webgl, width, height);
}
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
}
}
private clearSkybox() {
if (this.skybox !== undefined) {
this.skybox.texture.destroy();
this.skybox.assets.forEach(a => this.assetManager.release(a));
this.skybox = undefined;
}
}
private updateSkybox(camera: ICamera, props: SkyboxProps, onload?: (changed: boolean) => void) {
const tf = this.skybox?.props.faces;
const f = props.faces.params;
if (!f.nx || !f.ny || !f.nz || !f.px || !f.py || !f.pz) {
this.clearSkybox();
onload?.(false);
return;
}
if (!this.skybox || !tf || !areSkyboxTexturePropsEqual(props.faces, this.skybox.props.faces)) {
this.clearSkybox();
const { texture, assets } = getSkyboxTexture(this.webgl, this.assetManager, props.faces, errored => {
if (this.skybox) this.skybox.loaded = !errored;
onload?.(true);
});
this.skybox = { texture, props: { ...props }, assets, loaded: false };
ValueCell.update(this.renderable.values.tSkybox, texture);
this.renderable.update();
} else {
onload?.(false);
}
if (!this.skybox) return;
let cam = camera;
if (camera.state.mode === 'orthographic') {
this.camera.setState({ ...camera.state, mode: 'perspective' });
this.camera.update();
cam = this.camera;
}
const m = this.renderable.values.uViewDirectionProjectionInverse.ref.value;
Vec3.sub(this.dir, cam.state.position, cam.state.target);
Vec3.setMagnitude(this.dir, this.dir, 0.1);
Vec3.copy(this.position, this.dir);
Mat4.lookAt(m, this.position, this.target, cam.state.up);
Mat4.mul(m, cam.projection, m);
Mat4.invert(m, m);
ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m);
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness);
ValueCell.updateIfChanged(this.renderable.values.dVariant, 'skybox');
this.renderable.update();
}
private clearImage() {
if (this.image !== undefined) {
this.image.texture.destroy();
this.assetManager.release(this.image.asset);
this.image = undefined;
}
}
private updateImage(props: ImageProps, onload?: (loaded: boolean) => void) {
if (!props.source.params) {
this.clearImage();
onload?.(false);
return;
}
if (!this.image || !this.image.props.source.params || !areImageTexturePropsEqual(props.source, this.image.props.source)) {
this.clearImage();
const { texture, asset } = getImageTexture(this.webgl, this.assetManager, props.source, errored => {
if (this.image) this.image.loaded = !errored;
onload?.(true);
});
this.image = { texture, props: { ...props }, asset, loaded: false };
ValueCell.update(this.renderable.values.tImage, texture);
this.renderable.update();
} else {
onload?.(false);
}
if (!this.image) return;
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness);
ValueCell.updateIfChanged(this.renderable.values.uViewportAdjusted, props.coverage === 'viewport' ? true : false);
ValueCell.updateIfChanged(this.renderable.values.dVariant, 'image');
this.renderable.update();
}
private updateImageScaling() {
const v = this.renderable.values;
const [w, h] = v.uTexSize.ref.value;
const iw = this.image?.texture.getWidth() || 0;
const ih = this.image?.texture.getHeight() || 0;
const r = w / h;
const ir = iw / ih;
// responsive scaling with offset
if (r < ir) {
ValueCell.update(v.uImageScale, Vec2.set(v.uImageScale.ref.value, iw * h / ih, h));
} else {
ValueCell.update(v.uImageScale, Vec2.set(v.uImageScale.ref.value, w, ih * w / iw));
}
const [rw, rh] = v.uImageScale.ref.value;
const sr = rw / rh;
if (sr > r) {
ValueCell.update(v.uImageOffset, Vec2.set(v.uImageOffset.ref.value, (1 - r / sr) / 2, 0));
} else {
ValueCell.update(v.uImageOffset, Vec2.set(v.uImageOffset.ref.value, 0, (1 - sr / r) / 2));
}
}
private updateGradient(colorA: Color, colorB: Color, ratio: number, variant: 'horizontalGradient' | 'radialGradient', viewportAdjusted: boolean) {
ValueCell.update(this.renderable.values.uGradientColorA, Color.toVec3Normalized(this.renderable.values.uGradientColorA.ref.value, colorA));
ValueCell.update(this.renderable.values.uGradientColorB, Color.toVec3Normalized(this.renderable.values.uGradientColorB.ref.value, colorB));
ValueCell.updateIfChanged(this.renderable.values.uGradientRatio, ratio);
ValueCell.updateIfChanged(this.renderable.values.uViewportAdjusted, viewportAdjusted);
ValueCell.updateIfChanged(this.renderable.values.dVariant, variant);
this.renderable.update();
}
update(camera: ICamera, props: BackgroundProps, onload?: (changed: boolean) => void) {
if (props.variant.name === 'off') {
this.clearSkybox();
this.clearImage();
onload?.(false);
return;
} else if (props.variant.name === 'skybox') {
this.clearImage();
this.updateSkybox(camera, props.variant.params, onload);
} else if (props.variant.name === 'image') {
this.clearSkybox();
this.updateImage(props.variant.params, onload);
} else if (props.variant.name === 'horizontalGradient') {
this.clearSkybox();
this.clearImage();
this.updateGradient(props.variant.params.topColor, props.variant.params.bottomColor, props.variant.params.ratio, props.variant.name, props.variant.params.coverage === 'viewport' ? true : false);
onload?.(false);
} else if (props.variant.name === 'radialGradient') {
this.clearSkybox();
this.clearImage();
this.updateGradient(props.variant.params.centerColor, props.variant.params.edgeColor, props.variant.params.ratio, props.variant.name, props.variant.params.coverage === 'viewport' ? true : false);
onload?.(false);
}
const { x, y, width, height } = camera.viewport;
ValueCell.update(this.renderable.values.uViewport, Vec4.set(this.renderable.values.uViewport.ref.value, x, y, width, height));
}
isEnabled(props: BackgroundProps) {
return !!(
(this.skybox && this.skybox.loaded) ||
(this.image && this.image.loaded) ||
props.variant.name === 'horizontalGradient' ||
props.variant.name === 'radialGradient'
);
}
private isReady() {
return !!(
(this.skybox && this.skybox.loaded) ||
(this.image && this.image.loaded) ||
this.renderable.values.dVariant.ref.value === 'horizontalGradient' ||
this.renderable.values.dVariant.ref.value === 'radialGradient'
);
}
render() {
if (!this.isReady()) return;
if (this.renderable.values.dVariant.ref.value === 'image') {
this.updateImageScaling();
}
if (isTimingMode) this.webgl.timer.mark('BackgroundPass.render');
this.renderable.render();
if (isTimingMode) this.webgl.timer.markEnd('BackgroundPass.render');
}
dispose() {
this.clearSkybox();
this.clearImage();
}
}
//
const SkyboxName = 'background-skybox';
type CubeAssets = { [k in keyof CubeFaces]: Asset };
function getCubeAssets(assetManager: AssetManager, faces: SkyboxProps['faces']): CubeAssets {
if (faces.name === 'urls') {
return {
nx: Asset.getUrlAsset(assetManager, faces.params.nx),
ny: Asset.getUrlAsset(assetManager, faces.params.ny),
nz: Asset.getUrlAsset(assetManager, faces.params.nz),
px: Asset.getUrlAsset(assetManager, faces.params.px),
py: Asset.getUrlAsset(assetManager, faces.params.py),
pz: Asset.getUrlAsset(assetManager, faces.params.pz),
};
} else {
return {
nx: faces.params.nx!,
ny: faces.params.ny!,
nz: faces.params.nz!,
px: faces.params.px!,
py: faces.params.py!,
pz: faces.params.pz!,
};
}
}
function getCubeFaces(assetManager: AssetManager, cubeAssets: CubeAssets): CubeFaces {
const resolve = (asset: Asset) => {
return assetManager.resolve(asset, 'binary').run().then(a => new Blob([a.data]));
};
return {
nx: resolve(cubeAssets.nx),
ny: resolve(cubeAssets.ny),
nz: resolve(cubeAssets.nz),
px: resolve(cubeAssets.px),
py: resolve(cubeAssets.py),
pz: resolve(cubeAssets.pz),
};
}
function getSkyboxHash(faces: SkyboxProps['faces']) {
if (faces.name === 'urls') {
return `${SkyboxName}_${faces.params.nx}|${faces.params.ny}|${faces.params.nz}|${faces.params.px}|${faces.params.py}|${faces.params.pz}`;
} else {
return `${SkyboxName}_${faces.params.nx?.id}|${faces.params.ny?.id}|${faces.params.nz?.id}|${faces.params.px?.id}|${faces.params.py?.id}|${faces.params.pz?.id}`;
}
}
function areSkyboxTexturePropsEqual(facesA: SkyboxProps['faces'], facesB: SkyboxProps['faces']) {
return getSkyboxHash(facesA) === getSkyboxHash(facesB);
}
function getSkyboxTexture(ctx: WebGLContext, assetManager: AssetManager, faces: SkyboxProps['faces'], onload?: (errored?: boolean) => void): { texture: Texture, assets: Asset[] } {
const cubeAssets = getCubeAssets(assetManager, faces);
const cubeFaces = getCubeFaces(assetManager, cubeAssets);
const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz];
const texture = ctx.resources.cubeTexture(cubeFaces, false, onload);
return { texture, assets };
}
//
const ImageName = 'background-image';
function getImageHash(source: ImageProps['source']) {
if (source.name === 'url') {
return `${ImageName}_${source.params}`;
} else {
return `${ImageName}_${source.params?.id}`;
}
}
function areImageTexturePropsEqual(sourceA: ImageProps['source'], sourceB: ImageProps['source']) {
return getImageHash(sourceA) === getImageHash(sourceB);
}
function getImageTexture(ctx: WebGLContext, assetManager: AssetManager, source: ImageProps['source'], onload?: (errored?: boolean) => void): { texture: Texture, asset: Asset } {
const texture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
const img = new Image();
img.onload = () => {
texture.load(img);
onload?.();
};
img.onerror = () => {
onload?.(true);
};
const asset = source.name === 'url'
? Asset.getUrlAsset(assetManager, source.params)
: source.params!;
assetManager.resolve(asset, 'binary').run().then(a => {
const blob = new Blob([a.data]);
img.src = URL.createObjectURL(blob);
});
return { texture, asset };
}
//
const BackgroundSchema = {
drawCount: ValueSpec('number'),
instanceCount: ValueSpec('number'),
aPosition: AttributeSpec('float32', 2, 0),
tSkybox: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tImage: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uImageScale: UniformSpec('v2'),
uImageOffset: UniformSpec('v2'),
uTexSize: UniformSpec('v2'),
uViewport: UniformSpec('v4'),
uViewportAdjusted: UniformSpec('b'),
uViewDirectionProjectionInverse: UniformSpec('m4'),
uGradientColorA: UniformSpec('v3'),
uGradientColorB: UniformSpec('v3'),
uGradientRatio: UniformSpec('f'),
uOpacity: UniformSpec('f'),
uSaturation: UniformSpec('f'),
uLightness: UniformSpec('f'),
dVariant: DefineSpec('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']),
};
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag);
type BackgroundRenderable = ComputeRenderable<Values<typeof BackgroundSchema>>
function getBackgroundRenderable(ctx: WebGLContext, width: number, height: number): BackgroundRenderable {
const values: Values<typeof BackgroundSchema> = {
drawCount: ValueCell.create(6),
instanceCount: ValueCell.create(1),
aPosition: ValueCell.create(QuadPositions),
tSkybox: ValueCell.create(createNullTexture()),
tImage: ValueCell.create(createNullTexture()),
uImageScale: ValueCell.create(Vec2()),
uImageOffset: ValueCell.create(Vec2()),
uTexSize: ValueCell.create(Vec2.create(width, height)),
uViewport: ValueCell.create(Vec4()),
uViewportAdjusted: ValueCell.create(true),
uViewDirectionProjectionInverse: ValueCell.create(Mat4()),
uGradientColorA: ValueCell.create(Vec3()),
uGradientColorB: ValueCell.create(Vec3()),
uGradientRatio: ValueCell.create(0.5),
uOpacity: ValueCell.create(1),
uSaturation: ValueCell.create(0),
uLightness: ValueCell.create(0),
dVariant: ValueCell.create('skybox'),
};
const schema = { ...BackgroundSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', SkyboxShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

View File

@@ -21,10 +21,11 @@ import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './pos
import { MarkingPass, MarkingProps } from './marking';
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
import { isTimingMode } from '../../mol-util/debug';
import { AssetManager } from '../../mol-util/assets';
type Props = {
postprocessing: PostprocessingProps
marking: MarkingProps
postprocessing: PostprocessingProps;
marking: MarkingProps;
transparentBackground: boolean;
}
@@ -50,7 +51,7 @@ export class DrawPass {
private copyFboTarget: CopyRenderable;
private copyFboPostprocessing: CopyRenderable;
private wboit: WboitPass | undefined;
private readonly wboit: WboitPass | undefined;
private readonly marking: MarkingPass;
readonly postprocessing: PostprocessingPass;
private readonly antialiasing: AntialiasingPass;
@@ -59,11 +60,10 @@ export class DrawPass {
return !!this.wboit?.supported;
}
constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) {
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean) {
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.packedDepth = !extensions.depthTexture;
@@ -79,7 +79,7 @@ export class DrawPass {
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
this.marking = new MarkingPass(webgl, width, height);
this.postprocessing = new PostprocessingPass(webgl, this);
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
@@ -120,14 +120,13 @@ export class DrawPass {
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
this.colorTarget.bind();
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
renderer.clear(true);
// render opaque primitives
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
renderer.clearDepth();
renderer.renderWboitOpaque(scene.primitives, camera, null);
if (scene.hasOpaque) {
renderer.renderWboitOpaque(scene.primitives, camera, null);
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
@@ -165,14 +164,17 @@ export class DrawPass {
if (toDrawingBuffer) {
this.drawTarget.bind();
} else {
this.colorTarget.bind();
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
} else {
this.colorTarget.bind();
}
}
renderer.clear(true);
renderer.renderBlendedOpaque(scene.primitives, camera, null);
if (scene.hasOpaque) {
renderer.renderBlendedOpaque(scene.primitives, camera, null);
}
if (!toDrawingBuffer) {
// do a depth pass if not rendering to drawing buffer and
@@ -235,7 +237,7 @@ export class DrawPass {
}
}
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, props: Props) {
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const volumeRendering = scene.volumes.renderables.length > 0;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
const antialiasingEnabled = AntialiasingPass.isEnabled(props.postprocessing);
@@ -245,54 +247,52 @@ export class DrawPass {
renderer.setViewport(x, y, width, height);
renderer.update(camera);
if (props.transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
this.drawTarget.bind();
renderer.clear(false);
}
if (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, props.transparentBackground, props.postprocessing);
this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
} else {
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, props.transparentBackground, props.postprocessing);
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
}
if (postprocessingEnabled) {
this.postprocessing.target.bind();
} else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) {
this.colorTarget.bind();
} else {
this.drawTarget.bind();
}
const target = postprocessingEnabled
? this.postprocessing.target
: !toDrawingBuffer || volumeRendering || this.wboitEnabled
? this.colorTarget
: this.drawTarget;
if (markingEnabled) {
if (scene.markerAverage > 0) {
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
if (markingDepthTest && scene.markerAverage !== 1) {
this.marking.depthTarget.bind();
renderer.clear(false, true);
renderer.renderMarkingDepth(scene.primitives, camera, null);
}
this.marking.maskTarget.bind();
if (markingEnabled && scene.markerAverage > 0) {
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
if (markingDepthTest && scene.markerAverage !== 1) {
this.marking.depthTarget.bind();
renderer.clear(false, true);
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
this.marking.update(props.marking);
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
renderer.renderMarkingDepth(scene.primitives, camera, null);
}
this.marking.maskTarget.bind();
renderer.clear(false, true);
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
this.marking.update(props.marking);
this.marking.render(camera.viewport, target);
} else {
target.bind();
}
if (helper.debug.isEnabled) {
helper.debug.syncVisibility();
renderer.renderBlended(helper.debug.scene, camera, null);
renderer.renderBlended(helper.debug.scene, camera);
}
if (helper.handle.isEnabled) {
renderer.renderBlended(helper.handle.scene, camera, null);
renderer.renderBlended(helper.handle.scene, camera);
}
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
renderer.renderBlended(helper.camera.scene, helper.camera.camera, null);
renderer.renderBlended(helper.camera.scene, helper.camera.camera);
}
if (antialiasingEnabled) {
@@ -314,15 +314,19 @@ export class DrawPass {
render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
if (isTimingMode) this.webgl.timer.mark('DrawPass.render');
const { renderer, camera, scene, helper } = ctx;
renderer.setTransparentBackground(props.transparentBackground);
this.postprocessing.setTransparentBackground(props.transparentBackground);
const transparentBackground = props.transparentBackground || this.postprocessing.background.isEnabled(props.postprocessing.background);
renderer.setTransparentBackground(transparentBackground);
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
renderer.setPixelRatio(this.webgl.pixelRatio);
if (StereoCamera.is(camera)) {
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, props);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, props);
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props);
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer, props);
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
}
if (isTimingMode) this.webgl.timer.markEnd('DrawPass.render');
}

View File

@@ -44,8 +44,8 @@ export class FxaaPass {
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -18,6 +18,7 @@ import { PixelData } from '../../mol-util/image';
import { Helper } from '../helper/helper';
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
import { MarkingParams } from './marking';
import { AssetManager } from '../../mol-util/assets';
export const ImageParams = {
transparentBackground: PD.Boolean(false),
@@ -47,10 +48,10 @@ export class ImagePass {
get width() { return this._width; }
get height() { return this._height; }
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
this.drawPass = new DrawPass(webgl, 128, 128, enableWboit);
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
@@ -63,6 +64,14 @@ export class ImagePass {
this.setSize(1024, 768);
}
updateBackground() {
return new Promise<void>(resolve => {
this.drawPass.postprocessing.background.update(this.camera, this.props.postprocessing.background, () => {
resolve();
});
});
}
setSize(width: number, height: number) {
if (width === this._width && height === this._height) return;

View File

@@ -64,8 +64,8 @@ export class MarkingPass {
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -82,8 +82,8 @@ export class MarkingPass {
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
}
setSize(width: number, height: number) {

View File

@@ -176,8 +176,8 @@ export class MultiSamplePass {
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
if (i === 0) {
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -192,8 +192,8 @@ export class MultiSamplePass {
compose.update();
this.bindOutputTarget(toDrawingBuffer);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
state.disable(gl.BLEND);
compose.render();
@@ -231,8 +231,8 @@ export class MultiSamplePass {
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
compose.render();
sampleIndex += 1;
} else {
@@ -267,8 +267,8 @@ export class MultiSamplePass {
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
if (sampleIndex === 0) {
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -283,8 +283,8 @@ export class MultiSamplePass {
drawPass.postprocessing.setOcclusionOffset(0, 0);
this.bindOutputTarget(toDrawingBuffer);
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
const accumulationWeight = sampleIndex * sampleWeight;
if (accumulationWeight > 0) {

View File

@@ -8,15 +8,16 @@ import { DrawPass } from './draw';
import { PickPass } from './pick';
import { MultiSamplePass } from './multi-sample';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { AssetManager } from '../../mol-util/assets';
export class Passes {
readonly draw: DrawPass;
readonly pick: PickPass;
readonly multiSample: MultiSamplePass;
constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
const { gl } = webgl;
this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
this.multiSample = new MultiSamplePass(webgl, this.draw);
}

View File

@@ -28,6 +28,8 @@ import { Color } from '../../mol-util/color';
import { FxaaParams, FxaaPass } from './fxaa';
import { SmaaParams, SmaaPass } from './smaa';
import { isTimingMode } from '../../mol-util/debug';
import { BackgroundParams, BackgroundPass } from './background';
import { AssetManager } from '../../mol-util/assets';
const OutlinesSchema = {
...QuadSchema,
@@ -91,7 +93,7 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uSamples: ValueCell.create([0.0, 0.0, 1.0]),
uSamples: ValueCell.create(getSamples(32)),
dNSamples: ValueCell.create(32),
uProjection: ValueCell.create(Mat4.identity()),
@@ -138,7 +140,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())),
uKernel: ValueCell.create([0.0]),
uKernel: ValueCell.create(getBlurKernel(15)),
dOcclusionKernelSize: ValueCell.create(15),
uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
@@ -171,15 +173,26 @@ function getBlurKernel(kernelSize: number): number[] {
return kernel;
}
function getSamples(vectorSamples: Vec3[], nSamples: number): number[] {
const RandomHemisphereVector: Vec3[] = [];
for (let i = 0; i < 256; i++) {
const v = Vec3();
v[0] = Math.random() * 2.0 - 1.0;
v[1] = Math.random() * 2.0 - 1.0;
v[2] = Math.random();
Vec3.normalize(v, v);
Vec3.scale(v, v, Math.random());
RandomHemisphereVector.push(v);
}
function getSamples(nSamples: number): number[] {
const samples = [];
for (let i = 0; i < nSamples; i++) {
let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
scale = 0.1 + scale * (1.0 - 0.1);
samples.push(vectorSamples[i][0] * scale);
samples.push(vectorSamples[i][1] * scale);
samples.push(vectorSamples[i][2] * scale);
samples.push(RandomHemisphereVector[i][0] * scale);
samples.push(RandomHemisphereVector[i][1] * scale);
samples.push(RandomHemisphereVector[i][2] * scale);
}
return samples;
@@ -274,12 +287,13 @@ export const PostprocessingParams = {
smaa: PD.Group(SmaaParams),
off: PD.Group({})
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
background: PD.Group(BackgroundParams, { isFlat: true }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
export class PostprocessingPass {
static isEnabled(props: PostprocessingProps) {
return props.occlusion.name === 'on' || props.outline.name === 'on';
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
}
static isOutlineEnabled(props: PostprocessingProps) {
@@ -291,7 +305,6 @@ export class PostprocessingPass {
private readonly outlinesTarget: RenderTarget;
private readonly outlinesRenderable: OutlinesRenderable;
private readonly randomHemisphereVector: Vec3[];
private readonly ssaoFramebuffer: Framebuffer;
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
@@ -318,7 +331,10 @@ export class PostprocessingPass {
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
}
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
private readonly bgColor = Vec3();
readonly background: BackgroundPass;
constructor(private readonly webgl: WebGLContext, assetManager: AssetManager, private readonly drawPass: DrawPass) {
const { colorTarget, depthTextureTransparent, depthTextureOpaque } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
@@ -334,16 +350,6 @@ export class PostprocessingPass {
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
this.randomHemisphereVector = [];
for (let i = 0; i < 256; i++) {
const v = Vec3();
v[0] = Math.random() * 2.0 - 1.0;
v[1] = Math.random() * 2.0 - 1.0;
v[2] = Math.random();
Vec3.normalize(v, v);
Vec3.scale(v, v, Math.random());
this.randomHemisphereVector.push(v);
}
this.ssaoFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
@@ -368,6 +374,8 @@ export class PostprocessingPass {
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture);
this.background = new BackgroundPass(webgl, assetManager, width, height);
}
setSize(width: number, height: number) {
@@ -391,6 +399,8 @@ export class PostprocessingPass {
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
this.background.setSize(width, height);
}
}
@@ -440,7 +450,7 @@ export class PostprocessingPass {
needsUpdateSsao = true;
this.nSamples = props.occlusion.params.samples;
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.nSamples));
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
}
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
@@ -538,8 +548,8 @@ export class PostprocessingPass {
state.depthMask(false);
const { x, y, width, height } = camera.viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
}
private occlusionOffset: [x: number, y: number] = [0, 0];
@@ -549,6 +559,11 @@ export class PostprocessingPass {
ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y));
}
private transparentBackground = false;
setTransparentBackground(value: boolean) {
this.transparentBackground = value;
}
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render');
this.updateState(camera, transparentBackground, backgroundColor, props);
@@ -583,8 +598,23 @@ export class PostprocessingPass {
}
const { gl, state } = this.webgl;
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
this.background.update(camera, props.background);
if (this.background.isEnabled(props.background)) {
if (this.transparentBackground) {
state.clearColor(0, 0, 0, 0);
} else {
Color.toVec3Normalized(this.bgColor, backgroundColor);
state.clearColor(this.bgColor[0], this.bgColor[1], this.bgColor[2], 1);
}
gl.clear(gl.COLOR_BUFFER_BIT);
state.enable(gl.BLEND);
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
this.background.render();
} else {
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
this.renderable.render();
if (isTimingMode) this.webgl.timer.markEnd('PostprocessingPass.render');

View File

@@ -71,8 +71,8 @@ export class SmaaPass {
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

View File

@@ -45,6 +45,8 @@ export interface Mesh {
readonly normalBuffer: ValueCell<Float32Array>,
/** Group buffer as array of group ids for each vertex wrapped in a value cell */
readonly groupBuffer: ValueCell<Float32Array>,
/** Indicates that group may vary within a triangle, wrapped in a value cell */
readonly varyingGroup: ValueCell<boolean>,
/** Bounding sphere of the mesh */
readonly boundingSphere: Sphere3D
@@ -95,6 +97,7 @@ export namespace Mesh {
indexBuffer: ValueCell.create(indices),
normalBuffer: ValueCell.create(normals),
groupBuffer: ValueCell.create(groups),
varyingGroup: ValueCell.create(false),
get boundingSphere() {
const newHash = hashCode(mesh);
if (newHash !== currentHash) {
@@ -686,6 +689,7 @@ export namespace Mesh {
aNormal: mesh.normalBuffer,
aGroup: mesh.groupBuffer,
elements: mesh.indexBuffer,
dVaryingGroup: mesh.varyingGroup,
boundingSphere: ValueCell.create(boundingSphere),
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),

View File

@@ -27,20 +27,18 @@ export const ColorAccumulateSchema = {
instanceCount: ValueSpec('number'),
stride: ValueSpec('number'),
uTotalCount: UniformSpec('i'),
uInstanceCount: UniformSpec('i'),
uGroupCount: UniformSpec('i'),
uGroupCount: UniformSpec('i', 'material'),
aTransform: AttributeSpec('float32', 16, 1),
aInstance: AttributeSpec('float32', 1, 1),
aSample: AttributeSpec('float32', 1, 0),
uGeoTexDim: UniformSpec('v2', 'buffered'),
tPosition: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uGeoTexDim: UniformSpec('v2', 'material'),
tPosition: TextureSpec('texture', 'rgba', 'float', 'nearest', 'material'),
tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest', 'material'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uColorTexDim: UniformSpec('v2', 'material'),
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest', 'material'),
dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']),
uCurrentSlice: UniformSpec('f'),
@@ -88,8 +86,6 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box:
ValueCell.updateIfChanged(v.instanceCount, input.instanceCount);
ValueCell.updateIfChanged(v.stride, stride);
ValueCell.updateIfChanged(v.uTotalCount, input.vertexCount);
ValueCell.updateIfChanged(v.uInstanceCount, input.instanceCount);
ValueCell.updateIfChanged(v.uGroupCount, input.groupCount);
ValueCell.update(v.aTransform, input.transformBuffer);
@@ -126,8 +122,6 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
instanceCount: ValueCell.create(input.instanceCount),
stride: ValueCell.create(stride),
uTotalCount: ValueCell.create(input.vertexCount),
uInstanceCount: ValueCell.create(input.instanceCount),
uGroupCount: ValueCell.create(input.groupCount),
aTransform: ValueCell.create(input.transformBuffer),
@@ -325,8 +319,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
if (isTimingMode) webgl.timer.mark('ColorAccumulate.render');
setAccumulateDefaults(webgl);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
state.viewport(0, 0, width, height);
state.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
ValueCell.update(uCurrentY, 0);
let currCol = 0;
@@ -342,8 +336,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
// console.log({ i, currX, currY });
ValueCell.update(uCurrentX, currX);
ValueCell.update(uCurrentSlice, i);
gl.viewport(currX, currY, dx, dy);
gl.scissor(currX, currY, dx, dy);
state.viewport(currX, currY, dx, dy);
state.scissor(currX, currY, dx, dy);
accumulateRenderable.render();
++currCol;
currX += dx;
@@ -377,8 +371,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
setNormalizeDefaults(webgl);
texture.attachFramebuffer(framebuffer, 0);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
state.viewport(0, 0, width, height);
state.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
normalizeRenderable.render();
if (isTimingMode) webgl.timer.markEnd('ColorNormalize.render');

View File

@@ -38,6 +38,7 @@ export interface TextureMesh {
readonly vertexTexture: ValueCell<Texture>,
readonly groupTexture: ValueCell<Texture>,
readonly normalTexture: ValueCell<Texture>,
readonly varyingGroup: ValueCell<boolean>,
readonly doubleBuffer: TextureMesh.DoubleBuffer
readonly boundingSphere: Sphere3D
@@ -92,6 +93,7 @@ export namespace TextureMesh {
vertexTexture: ValueCell.create(vertexTexture),
groupTexture: ValueCell.create(groupTexture),
normalTexture: ValueCell.create(normalTexture),
varyingGroup: ValueCell.create(false),
doubleBuffer: new DoubleBuffer(),
boundingSphere: Sphere3D.clone(boundingSphere),
meta: {}
@@ -157,6 +159,7 @@ export namespace TextureMesh {
tPosition: textureMesh.vertexTexture,
tGroup: textureMesh.groupTexture,
tNormal: textureMesh.normalTexture,
dVaryingGroup: textureMesh.varyingGroup,
boundingSphere: ValueCell.create(boundingSphere),
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),

View File

@@ -225,8 +225,8 @@ export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>
function resetGl(webgl: WebGLContext, w: number) {
const { gl, state } = webgl;
gl.viewport(0, 0, w, w);
gl.scissor(0, 0, w, w);
state.viewport(0, 0, w, w);
state.scissor(0, 0, w, w);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);

View File

@@ -122,7 +122,7 @@ export interface HistogramPyramid {
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2, gridTexDim: Vec3): HistogramPyramid {
if (isTimingMode) ctx.timer.mark('createHistogramPyramid');
const { gl } = ctx;
const { gl, state } = ctx;
const w = inputTexture.getWidth();
const h = inputTexture.getHeight();
@@ -146,7 +146,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
const framebuffer = getFramebuffer('pyramid', ctx);
pyramidTex.attachFramebuffer(framebuffer, 0);
gl.viewport(0, 0, maxSizeX, maxSizeY);
state.viewport(0, 0, maxSizeX, maxSizeY);
if (isWebGL2(gl)) {
gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
} else {
@@ -157,7 +157,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i));
const renderable = getHistopyramidReductionRenderable(ctx, inputTexture, levelTexturesFramebuffers[0].texture);
ctx.state.currentRenderItemId = -1;
state.currentRenderItemId = -1;
setRenderingDefaults(ctx);
let offset = 0;
@@ -176,15 +176,15 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
ValueCell.update(renderable.values.tPreviousLevel, levelTexturesFramebuffers[levels - i].texture);
renderable.update();
}
ctx.state.currentRenderItemId = -1;
gl.viewport(0, 0, size, size);
gl.scissor(0, 0, size, size);
state.currentRenderItemId = -1;
state.viewport(0, 0, size, size);
state.scissor(0, 0, size, size);
if (isWebGL2(gl)) {
gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
} else {
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
state.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
renderable.render();
pyramidTex.bind(0);

View File

@@ -68,7 +68,7 @@ const sumInts = new Int32Array(4);
export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
if (isTimingMode) ctx.timer.mark('getHistopyramidSum');
const { gl, resources } = ctx;
const { gl, state, resources } = ctx;
const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture);
ctx.state.currentRenderItemId = -1;
@@ -89,7 +89,7 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
setRenderingDefaults(ctx);
gl.viewport(0, 0, 1, 1);
state.viewport(0, 0, 1, 1);
renderable.render();
gl.finish();

View File

@@ -85,7 +85,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, gridScale: Vec2) {
if (isTimingMode) ctx.timer.mark('calcActiveVoxels');
const { gl, resources } = ctx;
const { gl, state, resources } = ctx;
const width = volumeData.getWidth();
const height = volumeData.getHeight();
@@ -106,10 +106,10 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
activeVoxelsTex.attachFramebuffer(framebuffer, 0);
setRenderingDefaults(ctx);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
state.viewport(0, 0, width, height);
state.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
state.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
renderable.render();
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim);

View File

@@ -42,12 +42,13 @@ const IsosurfaceSchema = {
dPackedGroup: DefineSpec('boolean'),
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
dConstantGroup: DefineSpec('boolean'),
};
type IsosurfaceValues = Values<typeof IsosurfaceSchema>
const IsosurfaceName = 'isosurface';
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3): ComputeRenderable<IsosurfaceValues> {
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
@@ -66,17 +67,18 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
ValueCell.update(v.uGridTransform, transform);
ValueCell.update(v.uScale, scale);
ValueCell.update(v.dPackedGroup, packedGroup);
ValueCell.updateIfChanged(v.dPackedGroup, packedGroup);
ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join(''));
ValueCell.updateIfChanged(v.dConstantGroup, constantGroup);
ctx.namedComputeRenderables[IsosurfaceName].update();
} else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3) {
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) {
// console.log('uSize', Math.pow(2, levels))
const values: IsosurfaceValues = {
...QuadValues,
@@ -99,6 +101,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
dPackedGroup: ValueCell.create(packedGroup),
dAxisOrder: ValueCell.create(axisOrder.join('')),
dConstantGroup: ValueCell.create(constantGroup),
};
const schema = { ...IsosurfaceSchema };
@@ -119,12 +122,12 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.clearColor(0, 0, 0, 0);
}
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
if (isTimingMode) ctx.timer.mark('createIsosurfaceBuffers');
const { gl, resources, extensions } = ctx;
const { gl, state, resources, extensions } = ctx;
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
const width = pyramidTex.getWidth();
@@ -178,7 +181,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
groupTexture.attachFramebuffer(framebuffer, 1);
normalTexture.attachFramebuffer(framebuffer, 2);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
ctx.state.currentRenderItemId = -1;
framebuffer.bind();
@@ -189,7 +192,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
]);
setRenderingDefaults(ctx);
gl.viewport(0, 0, width, height);
state.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
renderable.render();
@@ -210,11 +213,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
*
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
*/
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
if (isTimingMode) ctx.timer.mark('extractIsosurface');
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, vertexTexture, groupTexture, normalTexture);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
if (isTimingMode) ctx.timer.markEnd('extractIsosurface');
return gv;

View File

@@ -125,8 +125,8 @@ export function readAlphaTexture(ctx: WebGLContext, texture: Texture) {
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);
state.viewport(0, 0, width, height);
state.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
copy.render();

View File

@@ -17,6 +17,7 @@ export const MeshSchema = {
aPosition: AttributeSpec('float32', 3, 0),
aNormal: AttributeSpec('float32', 3, 0),
elements: ElementsSpec('uint32'),
dVaryingGroup: DefineSpec('boolean'),
dFlatShaded: DefineSpec('boolean'),
uDoubleSided: UniformSpec('b', 'material'),
dFlipSided: DefineSpec('boolean'),

View File

@@ -36,6 +36,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
const attributeValues: AttributeValues = {};
const defineValues: DefineValues = {};
const textureValues: TextureValues = {};
const materialTextureValues: TextureValues = {};
const uniformValues: UniformValues = {};
const materialUniformValues: UniformValues = {};
const bufferedUniformValues: UniformValues = {};
@@ -44,7 +45,10 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
if (spec.type === 'attribute') attributeValues[k] = values[k];
if (spec.type === 'define') defineValues[k] = values[k];
// check if k exists in values to exclude global textures
if (spec.type === 'texture' && values[k] !== undefined) textureValues[k] = values[k];
if (spec.type === 'texture' && values[k] !== undefined) {
if (spec.variant === 'material') materialTextureValues[k] = values[k];
else textureValues[k] = values[k];
}
// check if k exists in values to exclude global uniforms
if (spec.type === 'uniform' && values[k] !== undefined) {
if (spec.variant === 'material') materialUniformValues[k] = values[k];
@@ -52,7 +56,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
else uniformValues[k] = values[k];
}
});
return { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues };
return { attributeValues, defineValues, textureValues, materialTextureValues, uniformValues, materialUniformValues, bufferedUniformValues };
}
export type Versions<T extends RenderableValues> = { [k in keyof T]: number }
@@ -76,9 +80,9 @@ export function UniformSpec<K extends UniformKind>(kind: K, variant?: 'material'
return { type: 'uniform', kind, variant };
}
export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter }
export function TextureSpec<K extends TextureKind>(kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter): TextureSpec<K> {
return { type: 'texture', kind, format, dataType, filter };
export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter, variant?: 'material' }
export function TextureSpec<K extends TextureKind>(kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter, variant?: 'material'): TextureSpec<K> {
return { type: 'texture', kind, format, dataType, filter, variant };
}
export type ElementsSpec<K extends ElementsKind> = { type: 'elements', kind: K }

View File

@@ -17,7 +17,7 @@ export const TextureMeshSchema = {
tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'),
tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'),
tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'),
dVaryingGroup: DefineSpec('boolean'),
dFlatShaded: DefineSpec('boolean'),
uDoubleSided: UniformSpec('b', 'material'),
dFlipSided: DefineSpec('boolean'),

View File

@@ -64,7 +64,7 @@ interface Renderer {
renderDepthTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlended: (group: Scene, camera: ICamera) => void
renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
@@ -359,8 +359,8 @@ namespace Renderer {
state.colorMask(true, true, true, true);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
globalUniformsNeedUpdate = true;
state.currentRenderItemId = -1;
@@ -475,9 +475,13 @@ namespace Renderer {
if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingMask');
};
const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
renderBlendedOpaque(group, camera, depthTexture);
renderBlendedTransparent(group, camera, depthTexture);
const renderBlended = (scene: Scene, camera: ICamera) => {
if (scene.hasOpaque) {
renderBlendedOpaque(scene, camera, null);
}
if (scene.opacityAverage < 1) {
renderBlendedTransparent(scene, camera, null);
}
};
const renderBlendedOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
@@ -591,7 +595,7 @@ namespace Renderer {
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit', Flag.None);
}
}
@@ -714,8 +718,8 @@ namespace Renderer {
}
},
setViewport: (x: number, y: number, width: number, height: number) => {
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
Viewport.set(viewport, x, y, width, height);
ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height));

View File

@@ -80,8 +80,12 @@ interface Scene extends Object3D {
has: (o: GraphicsRenderObject) => boolean
clear: () => void
forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
/** Marker average of primitive renderables */
readonly markerAverage: number
/** Opacity average of primitive renderables */
readonly opacityAverage: number
/** Is `true` if any primitive renderable (possibly) has any opaque part */
readonly hasOpaque: boolean
}
namespace Scene {
@@ -103,6 +107,7 @@ namespace Scene {
let markerAverage = 0;
let opacityAverage = 0;
let hasOpaque = false;
const object3d = Object3D.create();
const { view, position, direction, up } = object3d;
@@ -160,7 +165,9 @@ namespace Scene {
}
renderables.sort(renderableSort);
markerAverage = calculateMarkerAverage();
opacityAverage = calculateOpacityAverage();
hasOpaque = calculateHasOpaque();
return true;
}
@@ -182,7 +189,10 @@ namespace Scene {
const newVisibleHash = computeVisibleHash();
if (newVisibleHash !== visibleHash) {
boundingSphereVisibleDirty = true;
markerAverage = calculateMarkerAverage();
opacityAverage = calculateOpacityAverage();
hasOpaque = calculateHasOpaque();
visibleHash = newVisibleHash;
return true;
} else {
return false;
@@ -212,12 +222,27 @@ namespace Scene {
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(p.values.alpha.ref.value * p.state.alphaFactor, 0, 1);
const xray = p.values.dXrayShaded?.ref.value ? 0.5 : 1;
opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray;
const fuzzy = p.values.dPointStyle?.ref.value === 'fuzzy' ? 0.5 : 1;
const text = p.values.dGeometryType.ref.value === 'text' ? 0.5 : 1;
opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray * fuzzy * text;
count += 1;
}
return count > 0 ? opacityAverage / count : 0;
}
function calculateHasOpaque() {
if (primitives.length === 0) return false;
for (let i = 0, il = primitives.length; i < il; ++i) {
const p = primitives[i];
if (!p.state.visible) continue;
if (p.state.opaque) return true;
if (p.state.alphaFactor === 1 && p.values.alpha.ref.value === 1 && p.values.transparencyAverage.ref.value !== 1) return true;
if (p.values.dTransparentBackfaces?.ref.value === 'opaque') return true;
}
return false;
}
return {
view, position, direction, up,
@@ -245,6 +270,7 @@ namespace Scene {
}
markerAverage = calculateMarkerAverage();
opacityAverage = calculateOpacityAverage();
hasOpaque = calculateHasOpaque();
},
add: (o: GraphicsRenderObject) => commitQueue.add(o),
remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
@@ -281,7 +307,6 @@ namespace Scene {
if (boundingSphereVisibleDirty) {
calculateBoundingSphere(renderables, boundingSphereVisible, true);
boundingSphereVisibleDirty = false;
visibleHash = computeVisibleHash();
}
return boundingSphereVisible;
},
@@ -291,6 +316,9 @@ namespace Scene {
get opacityAverage() {
return opacityAverage;
},
get hasOpaque() {
return hasOpaque;
},
};
}
}

View File

@@ -292,7 +292,9 @@ const glsl300VertPrefixCommon = `
const glsl300FragPrefixCommon = `
#define varying in
#define texture2D texture
#define textureCube texture
#define texture2DLodEXT textureLod
#define textureCubeLodEXT textureLod
#define gl_FragColor out_FragData0
#define gl_FragDepthEXT gl_FragDepth

View File

@@ -0,0 +1,85 @@
export const background_frag = `
precision mediump float;
precision mediump samplerCube;
precision mediump sampler2D;
#if defined(dVariant_skybox)
uniform samplerCube tSkybox;
uniform mat4 uViewDirectionProjectionInverse;
uniform float uOpacity;
uniform float uSaturation;
uniform float uLightness;
#elif defined(dVariant_image)
uniform sampler2D tImage;
uniform vec2 uImageScale;
uniform vec2 uImageOffset;
uniform float uOpacity;
uniform float uSaturation;
uniform float uLightness;
#elif defined(dVariant_horizontalGradient) || defined(dVariant_radialGradient)
uniform vec3 uGradientColorA;
uniform vec3 uGradientColorB;
uniform float uGradientRatio;
#endif
uniform vec2 uTexSize;
uniform vec4 uViewport;
uniform bool uViewportAdjusted;
varying vec4 vPosition;
// TODO: add as general pp option to remove banding?
// Iestyn's RGB dither from http://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf
vec3 ScreenSpaceDither(vec2 vScreenPos) {
vec3 vDither = vec3(dot(vec2(171.0, 231.0), vScreenPos.xy));
vDither.rgb = fract(vDither.rgb / vec3(103.0, 71.0, 97.0));
return vDither.rgb / 255.0;
}
vec3 saturateColor(vec3 c, float amount) {
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
vec3 intensity = vec3(dot(c, W));
return mix(intensity, c, 1.0 + amount);
}
vec3 lightenColor(vec3 c, float amount) {
return c + amount;
}
void main() {
#if defined(dVariant_skybox)
vec4 t = uViewDirectionProjectionInverse * vPosition;
gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w));
gl_FragColor.a = uOpacity;
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);
#elif defined(dVariant_image)
vec2 coords;
if (uViewportAdjusted) {
coords = ((gl_FragCoord.xy - uViewport.xy) * (uTexSize / uViewport.zw) / uImageScale) + uImageOffset;
} else {
coords = (gl_FragCoord.xy / uImageScale) + uImageOffset;
}
gl_FragColor = texture2D(tImage, vec2(coords.x, 1.0 - coords.y));
gl_FragColor.a = uOpacity;
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);
#elif defined(dVariant_horizontalGradient)
float d;
if (uViewportAdjusted) {
d = ((gl_FragCoord.y - uViewport.y) * (uTexSize.y / uViewport.w) / uTexSize.y) + 1.0 - (uGradientRatio * 2.0);
} else {
d = (gl_FragCoord.y / uTexSize.y) + 1.0 - (uGradientRatio * 2.0);
}
gl_FragColor = vec4(mix(uGradientColorB, uGradientColorA, clamp(d, 0.0, 1.0)), 1.0);
gl_FragColor.rgb += ScreenSpaceDither(gl_FragCoord.xy);
#elif defined(dVariant_radialGradient)
float d;
if (uViewportAdjusted) {
d = distance(vec2(0.5), (gl_FragCoord.xy - uViewport.xy) * (uTexSize / uViewport.zw) / uTexSize) + uGradientRatio - 0.5;
} else {
d = distance(vec2(0.5), gl_FragCoord.xy / uTexSize) + uGradientRatio - 0.5;
}
gl_FragColor = vec4(mix(uGradientColorB, uGradientColorA, 1.0 - clamp(d, 0.0, 1.0)), 1.0);
gl_FragColor.rgb += ScreenSpaceDither(gl_FragCoord.xy);
#endif
}
`;

View File

@@ -0,0 +1,12 @@
export const background_vert = `
precision mediump float;
attribute vec2 aPosition;
varying vec4 vPosition;
void main() {
vPosition = vec4(aPosition, 1.0, 1.0);
gl_Position = vec4(aPosition, 1.0, 1.0);
}
`;

View File

@@ -27,7 +27,7 @@ uniform float uBumpiness;
varying vec4 vSubstance;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100
#if __VERSION__ == 100 || !defined(dVaryingGroup)
#ifdef requiredDrawBuffers
varying vec4 vObject;
varying vec4 vInstance;

View File

@@ -55,7 +55,7 @@ uniform float uBumpiness;
#endif
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100
#if __VERSION__ == 100 || !defined(dVaryingGroup)
#ifdef requiredDrawBuffers
varying vec4 vObject;
varying vec4 vInstance;

View File

@@ -14,10 +14,10 @@ uniform int uMarkingType;
uniform vec3 uClipObjectScale[dClipObjectCount];
#if defined(dClipping)
#if __VERSION__ == 100 || defined(dClippingType_instance)
#if __VERSION__ == 100 || defined(dClippingType_instance) || !defined(dVaryingGroup)
varying float vClipping;
#else
flat in float vClipping; // avoid if possible, causes slowdown, ASR
flat in float vClipping;
#endif
#endif
#endif
@@ -32,10 +32,10 @@ uniform int uMarkingType;
#if defined(dNeedsMarker)
uniform float uMarker;
#if __VERSION__ == 100 || defined(dMarkerType_instance)
#if __VERSION__ == 100 || defined(dMarkerType_instance) || !defined(dVaryingGroup)
varying float vMarker;
#else
flat in float vMarker; // avoid if possible, causes slowdown, ASR
flat in float vMarker;
#endif
#endif

View File

@@ -21,10 +21,10 @@ uniform int uPickType;
#if defined(dClipping)
uniform vec2 uClippingTexDim;
uniform sampler2D tClipping;
#if __VERSION__ == 100 || defined(dClippingType_instance)
#if __VERSION__ == 100 || defined(dClippingType_instance) || !defined(dVaryingGroup)
varying float vClipping;
#else
flat out float vClipping; // avoid if possible, causes slowdown, ASR
flat out float vClipping;
#endif
#endif
#endif
@@ -33,10 +33,10 @@ uniform int uPickType;
uniform float uMarker;
uniform vec2 uMarkerTexDim;
uniform sampler2D tMarker;
#if __VERSION__ == 100 || defined(dMarkerType_instance)
#if __VERSION__ == 100 || defined(dMarkerType_instance) || !defined(dVaryingGroup)
varying float vMarker;
#else
flat out float vMarker; // avoid if possible, causes slowdown, ASR
flat out float vMarker;
#endif
#endif
@@ -44,7 +44,10 @@ varying vec3 vModelPosition;
varying vec3 vViewPosition;
#if defined(noNonInstancedActiveAttribs)
#define VertexID gl_VertexID
#define VertexID gl_VertexID // for testing
// // int() is needed for some Safari versions
// // see https://bugs.webkit.org/show_bug.cgi?id=244152
// #define VertexID int(gl_VertexID)
#else
attribute float aVertex;
#define VertexID int(aVertex)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,7 +10,6 @@ precision highp float;
#include common
#include read_from_texture
uniform int uTotalCount;
uniform int uGroupCount;
attribute float aSample;

View File

@@ -268,9 +268,9 @@ void main(void) {
gl_FragData[0].xyz = (uGridTransform * vec4(b0 + t * (b0 - b1), 1.0)).xyz;
// group id
#if __VERSION__ == 100
#if __VERSION__ == 100 || defined(dConstantGroup)
// webgl1 does not support 'flat' interpolation (i.e. no interpolation)
// so we ensure a constant group id per triangle here
// ensure a constant group id per triangle as needed
#ifdef dPackedGroup
gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0);
#else

View File

@@ -142,12 +142,12 @@ export function readPixels(gl: GLRenderingContext, x: number, y: number, width:
if (isDebugMode) checkError(gl);
}
function getDrawingBufferPixelData(gl: GLRenderingContext) {
function getDrawingBufferPixelData(gl: GLRenderingContext, state: WebGLState) {
const w = gl.drawingBufferWidth;
const h = gl.drawingBufferHeight;
const buffer = new Uint8Array(w * h * 4);
unbindFramebuffer(gl);
gl.viewport(0, 0, w, h);
state.viewport(0, 0, w, h);
readPixels(gl, 0, 0, w, h, buffer);
return PixelData.flipY(PixelData.create(buffer, w, h));
}
@@ -164,6 +164,7 @@ function createStats() {
renderbuffer: 0,
shader: 0,
texture: 0,
cubeTexture: 0,
vertexArray: 0,
},
@@ -345,15 +346,15 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
readPixelsAsync,
waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl),
waitForGpuCommandsCompleteSync: () => waitForGpuCommandsCompleteSync(gl),
getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl),
getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl, state),
clear: (red: number, green: number, blue: number, alpha: number) => {
unbindFramebuffer(gl);
state.enable(gl.SCISSOR_TEST);
state.depthMask(true);
state.colorMask(true, true, true, true);
state.clearColor(red, green, blue, alpha);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.scissor(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
state.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
state.scissor(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
},

View File

@@ -118,7 +118,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
(schema as any).aVertex = AttributeSpec('float32', 1, 0);
}
const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues } = splitValues(schema, values);
const { attributeValues, defineValues, textureValues, materialTextureValues, uniformValues, materialUniformValues, bufferedUniformValues } = splitValues(schema, values);
const uniformValueEntries = Object.entries(uniformValues);
const materialUniformValueEntries = Object.entries(materialUniformValues);
@@ -136,6 +136,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
}
const textures = createTextures(ctx, schema, textureValues);
const materialTextures = createTextures(ctx, schema, materialTextureValues);
const attributeBuffers = createAttributeBuffers(ctx, schema, attributeValues);
let elementsBuffer: ElementsBuffer | undefined;
@@ -149,8 +150,8 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null;
}
let drawCount = values.drawCount.ref.value;
let instanceCount = values.instanceCount.ref.value;
let drawCount: number = values.drawCount.ref.value;
let instanceCount: number = values.instanceCount.ref.value;
stats.drawCount += drawCount;
stats.instanceCount += instanceCount;
@@ -167,7 +168,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
getProgram: (variant: T) => programs[variant],
render: (variant: T, sharedTexturesCount: number) => {
if (drawCount === 0 || instanceCount === 0 || ctx.isContextLost) return;
if (drawCount === 0 || instanceCount === 0) return;
const program = programs[variant];
if (program.id === currentProgramId && state.currentRenderItemId === id) {
program.setUniforms(uniformValueEntries);
@@ -180,6 +181,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
// console.log('program.id changed or materialId changed/-1', materialId)
if (program.id !== state.currentProgramId) program.use();
program.setUniforms(materialUniformValueEntries);
program.bindTextures(materialTextures, sharedTexturesCount + textures.length);
state.currentMaterialId = materialId;
currentProgramId = program.id;
}
@@ -314,6 +316,22 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
}
}
for (let i = 0, il = materialTextures.length; i < il; ++i) {
const [k, texture] = materialTextures[i];
const value = materialTextureValues[k];
if (value.ref.version !== versions[k]) {
// update of textures with kind 'texture' is done externally
if (schema[k].kind !== 'texture') {
// console.log('texture version changed, uploading image', k);
texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>);
valueChanges.textures = true;
} else {
materialTextures[i][1] = value.ref.value as Texture;
}
versions[k] = value.ref.version;
}
}
for (let i = 0, il = backBufferUniformValueEntries.length; i < il; ++i) {
const [k, uniform] = backBufferUniformValueEntries[i];
if (uniform.ref.version !== versions[k]) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -17,7 +17,7 @@ import { hashString, hashFnv32a } from '../../mol-data/util';
import { DefineValues, ShaderCode } from '../shader-code';
import { RenderableSchema } from '../renderable/schema';
import { createRenderbuffer, Renderbuffer, RenderbufferAttachment, RenderbufferFormat } from './renderbuffer';
import { Texture, TextureKind, TextureFormat, TextureType, TextureFilter, createTexture } from './texture';
import { Texture, TextureKind, TextureFormat, TextureType, TextureFilter, createTexture, CubeFaces, createCubeTexture } from './texture';
import { VertexArray, createVertexArray } from './vertex-array';
function defineValueHash(v: boolean | number | string): number {
@@ -59,6 +59,7 @@ export interface WebGLResources {
renderbuffer: (format: RenderbufferFormat, attachment: RenderbufferAttachment, width: number, height: number) => Renderbuffer
shader: (type: ShaderType, source: string) => Shader
texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => Texture,
cubeTexture: (faces: CubeFaces, mipaps: boolean, onload?: () => void) => Texture,
vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => VertexArray,
getByteCounts: () => ByteCounts
@@ -76,6 +77,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
renderbuffer: new Set<Resource>(),
shader: new Set<Resource>(),
texture: new Set<Resource>(),
cubeTexture: new Set<Resource>(),
vertexArray: new Set<Resource>(),
};
@@ -137,6 +139,9 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => {
return wrap('texture', createTexture(gl, extensions, kind, format, type, filter));
},
cubeTexture: (faces: CubeFaces, mipmaps: boolean, onload?: () => void) => {
return wrap('cubeTexture', createCubeTexture(gl, faces, mipmaps, onload));
},
vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => {
return wrap('vertexArray', createVertexArray(gl, extensions, program, attributeBuffers, elementsBuffer));
},
@@ -146,6 +151,9 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
sets.texture.forEach(r => {
texture += (r as Texture).getByteCount();
});
sets.cubeTexture.forEach(r => {
texture += (r as Texture).getByteCount();
});
let attribute = 0;
sets.attribute.forEach(r => {

View File

@@ -69,6 +69,9 @@ export type WebGLState = {
clearVertexAttribsState: () => void
disableUnusedVertexAttribs: () => void
viewport: (x: number, y: number, width: number, height: number) => void
scissor: (x: number, y: number, width: number, height: number) => void
reset: () => void
}
@@ -95,6 +98,9 @@ export function createState(gl: GLRenderingContext): WebGLState {
let maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
const vertexAttribsState: number[] = [];
let currentViewport: [number, number, number, number] = gl.getParameter(gl.VIEWPORT);
let currentScissor: [number, number, number, number] = gl.getParameter(gl.SCISSOR_BOX);
const clearVertexAttribsState = () => {
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
@@ -222,6 +228,26 @@ export function createState(gl: GLRenderingContext): WebGLState {
}
},
viewport: (x: number, y: number, width: number, height: number) => {
if (x !== currentViewport[0] || y !== currentViewport[1] || width !== currentViewport[2] || height !== currentViewport[3]) {
gl.viewport(x, y, width, height);
currentViewport[0] = x;
currentViewport[1] = y;
currentViewport[2] = width;
currentViewport[3] = height;
}
},
scissor: (x: number, y: number, width: number, height: number) => {
if (x !== currentScissor[0] || y !== currentScissor[1] || width !== currentScissor[2] || height !== currentScissor[3]) {
gl.scissor(x, y, width, height);
currentScissor[0] = x;
currentScissor[1] = y;
currentScissor[2] = width;
currentScissor[3] = height;
}
},
reset: () => {
enabledCapabilities = {};
@@ -247,6 +273,9 @@ export function createState(gl: GLRenderingContext): WebGLState {
for (let i = 0; i < maxVertexAttribs; ++i) {
vertexAttribsState[i] = 0;
}
currentViewport = gl.getParameter(gl.VIEWPORT);
currentScissor = gl.getParameter(gl.SCISSOR_BOX);
}
};
}

View File

@@ -11,8 +11,9 @@ import { RenderableSchema } from '../renderable/schema';
import { idFactory } from '../../mol-util/id-factory';
import { Framebuffer } from './framebuffer';
import { isWebGL2, GLRenderingContext } from './compat';
import { ValueOf } from '../../mol-util/type-helpers';
import { isPromiseLike, ValueOf } from '../../mol-util/type-helpers';
import { WebGLExtensions } from './extensions';
import { objectForEach } from '../../mol-util/object';
const getNextTextureId = idFactory();
@@ -423,6 +424,123 @@ export function loadImageTexture(src: string, cell: ValueCell<Texture>, texture:
//
export type CubeSide = 'nx' | 'ny' | 'nz' | 'px' | 'py' | 'pz';
export type CubeFaces = {
[k in CubeSide]: string | File | Promise<Blob>;
}
export function getCubeTarget(gl: GLRenderingContext, side: CubeSide): number {
switch (side) {
case 'nx': return gl.TEXTURE_CUBE_MAP_NEGATIVE_X;
case 'ny': return gl.TEXTURE_CUBE_MAP_NEGATIVE_Y;
case 'nz': return gl.TEXTURE_CUBE_MAP_NEGATIVE_Z;
case 'px': return gl.TEXTURE_CUBE_MAP_POSITIVE_X;
case 'py': return gl.TEXTURE_CUBE_MAP_POSITIVE_Y;
case 'pz': return gl.TEXTURE_CUBE_MAP_POSITIVE_Z;
}
}
export function createCubeTexture(gl: GLRenderingContext, faces: CubeFaces, mipmaps: boolean, onload?: (errored?: boolean) => void): Texture {
const target = gl.TEXTURE_CUBE_MAP;
const filter = gl.LINEAR;
const internalFormat = gl.RGBA;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
let size = 0;
const texture = gl.createTexture();
gl.bindTexture(target, texture);
let loadedCount = 0;
objectForEach(faces, (source, side) => {
if (!source) return;
const level = 0;
const cubeTarget = getCubeTarget(gl, side as CubeSide);
const image = new Image();
if (source instanceof File) {
image.src = URL.createObjectURL(source);
} else if (isPromiseLike(source)) {
source.then(blob => {
image.src = URL.createObjectURL(blob);
});
} else {
image.src = source;
}
image.addEventListener('load', () => {
if (size === 0) size = image.width;
gl.texImage2D(cubeTarget, level, internalFormat, size, size, 0, format, type, null);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.bindTexture(target, texture);
gl.texImage2D(cubeTarget, level, internalFormat, format, type, image);
loadedCount += 1;
if (loadedCount === 6) {
if (!destroyed) {
if (mipmaps) {
gl.generateMipmap(target);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
} else {
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter);
}
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter);
}
onload?.(destroyed);
}
});
image.addEventListener('error', () => {
onload?.(true);
});
});
let destroyed = false;
return {
id: getNextTextureId(),
target,
format,
internalFormat,
type,
filter,
getWidth: () => size,
getHeight: () => size,
getDepth: () => 0,
getByteCount: () => {
return getByteCount('rgba', 'ubyte', size, size, 0) * 6 * (mipmaps ? 2 : 1);
},
define: () => {},
load: () => {},
bind: (id: TextureId) => {
gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(target, texture);
},
unbind: (id: TextureId) => {
gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(target, null);
},
attachFramebuffer: () => {},
detachFramebuffer: () => {},
reset: () => {},
destroy: () => {
if (destroyed) return;
gl.deleteTexture(texture);
destroyed = true;
},
};
}
//
export function createNullTexture(gl?: GLRenderingContext): Texture {
const target = gl?.TEXTURE_2D ?? 3553;
return {

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.359, IHM 1.17, MA 1.4.1.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.359, IHM 1.17, MA 1.4.1.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.359, IHM 1.17, MA 1.4.1.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
*
* @author molstar/ciftools package
*/

View File

@@ -42,7 +42,7 @@ const GaussianDensitySchema = {
uAlpha: UniformSpec('f', 'material'),
uResolution: UniformSpec('f', 'material'),
uRadiusFactorInv: UniformSpec('f', 'material'),
tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest', 'material'),
dGridTexType: DefineSpec('string', ['2d', '3d']),
dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
@@ -166,8 +166,8 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
state.currentRenderItemId = -1;
fbTex.attachFramebuffer(framebuffer, 0);
if (clear) {
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
state.viewport(0, 0, width, height);
state.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
}
ValueCell.update(uCurrentY, 0);
@@ -184,8 +184,8 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
// console.log({ i, currX, currY });
ValueCell.update(uCurrentX, currX);
ValueCell.update(uCurrentSlice, i);
gl.viewport(currX, currY, dx, dy);
gl.scissor(currX, currY, dx, dy);
state.viewport(currX, currY, dx, dy);
state.scissor(currX, currY, dx, dy);
renderable.render();
++currCol;
currX += dx;
@@ -232,8 +232,8 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
const framebuffer = getFramebuffer(webgl);
framebuffer.bind();
setRenderingDefaults(webgl);
gl.viewport(0, 0, dx, dy);
gl.scissor(0, 0, dx, dy);
state.viewport(0, 0, dx, dy);
state.scissor(0, 0, dx, dy);
if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
? resources.texture('volume-float16', 'rgba', 'fp16', 'linear')

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -124,11 +124,19 @@ namespace Box3D {
}
export function containsVec3(box: Box3D, v: Vec3) {
return (
return !(
v[0] < box.min[0] || v[0] > box.max[0] ||
v[1] < box.min[1] || v[1] > box.max[1] ||
v[2] < box.min[2] || v[2] > box.max[2]
) ? false : true;
);
}
export function overlaps(a: Box3D, b: Box3D) {
return !(
a.max[0] < b.min[0] || a.min[0] > b.max[0] ||
a.max[1] < b.min[1] || a.min[1] > b.max[1] ||
a.max[2] < b.min[2] || a.min[2] > b.max[2]
);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -9,6 +9,7 @@ import { Vec3 } from '../3d/vec3';
import { svd } from './svd';
import { NumberArray } from '../../../mol-util/type-helpers';
import { Axes3D } from '../../geometry';
import { EPSILON } from '../3d/common';
export { PrincipalAxes };
@@ -58,10 +59,15 @@ namespace PrincipalAxes {
return Axes3D.create(origin, dirA, dirB, dirC);
}
export function calculateNormalizedAxes(momentsAxes: Axes3D): Axes3D {
const a = Axes3D.clone(momentsAxes);
if (Vec3.magnitude(a.dirC) < EPSILON) {
Vec3.cross(a.dirC, a.dirA, a.dirB);
}
return Axes3D.normalize(a, a);
}
const tmpBoxVec = Vec3();
const tmpBoxVecA = Vec3();
const tmpBoxVecB = Vec3();
const tmpBoxVecC = Vec3();
/**
* Get the scale/length for each dimension for a box around the axes
* to enclose the given positions
@@ -82,13 +88,11 @@ namespace PrincipalAxes {
const t = Vec3();
const center = momentsAxes.origin;
const normVecA = Vec3.normalize(tmpBoxVecA, momentsAxes.dirA);
const normVecB = Vec3.normalize(tmpBoxVecB, momentsAxes.dirB);
const normVecC = Vec3.normalize(tmpBoxVecC, momentsAxes.dirC);
const a = calculateNormalizedAxes(momentsAxes);
for (let i = 0, il = positions.length; i < il; i += 3) {
Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecA, center);
const dp1 = Vec3.dot(normVecA, Vec3.normalize(t, Vec3.sub(t, p, center)));
Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), a.dirA, center);
const dp1 = Vec3.dot(a.dirA, Vec3.normalize(t, Vec3.sub(t, p, center)));
const dt1 = Vec3.distance(p, center);
if (dp1 > 0) {
if (dt1 > d1a) d1a = dt1;
@@ -96,8 +100,8 @@ namespace PrincipalAxes {
if (dt1 > d1b) d1b = dt1;
}
Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecB, center);
const dp2 = Vec3.dot(normVecB, Vec3.normalize(t, Vec3.sub(t, p, center)));
Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), a.dirB, center);
const dp2 = Vec3.dot(a.dirB, Vec3.normalize(t, Vec3.sub(t, p, center)));
const dt2 = Vec3.distance(p, center);
if (dp2 > 0) {
if (dt2 > d2a) d2a = dt2;
@@ -105,8 +109,8 @@ namespace PrincipalAxes {
if (dt2 > d2b) d2b = dt2;
}
Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecC, center);
const dp3 = Vec3.dot(normVecC, Vec3.normalize(t, Vec3.sub(t, p, center)));
Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), a.dirC, center);
const dp3 = Vec3.dot(a.dirC, Vec3.normalize(t, Vec3.sub(t, p, center)));
const dt3 = Vec3.distance(p, center);
if (dp3 > 0) {
if (dt3 > d3a) d3a = dt3;
@@ -115,16 +119,16 @@ namespace PrincipalAxes {
}
}
const dirA = Vec3.setMagnitude(Vec3(), normVecA, (d1a + d1b) / 2);
const dirB = Vec3.setMagnitude(Vec3(), normVecB, (d2a + d2b) / 2);
const dirC = Vec3.setMagnitude(Vec3(), normVecC, (d3a + d3b) / 2);
const dirA = Vec3.setMagnitude(Vec3(), a.dirA, (d1a + d1b) / 2);
const dirB = Vec3.setMagnitude(Vec3(), a.dirB, (d2a + d2b) / 2);
const dirC = Vec3.setMagnitude(Vec3(), a.dirC, (d3a + d3b) / 2);
const origin = Vec3();
const addCornerHelper = function (d1: number, d2: number, d3: number) {
Vec3.copy(tmpBoxVec, center);
Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecA, d1);
Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecB, d2);
Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecC, d3);
Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirA, d1);
Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirB, d2);
Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirC, d3);
Vec3.add(origin, origin, tmpBoxVec);
};
addCornerHelper(d1a, d2a, d3a);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Schäfer, Marco <marco.schaefer@uni-tuebingen.de>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -19,6 +19,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ColorNames } from '../../mol-util/color/names';
import { deepClone } from '../../mol-util/object';
import { stringToWords } from '../../mol-util/string';
import { ValueCell } from '../../mol-util/value-cell';
// TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html
// TODO support missing face element
@@ -170,6 +171,9 @@ async function getMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, gro
const m = MeshBuilder.getMesh(builderState);
if (!hasNormals) Mesh.computeNormals(m);
// TODO: check if needed
ValueCell.updateIfChanged(m.varyingGroup, true);
return m;
}

View File

@@ -80,7 +80,10 @@ export async function getMolModels(mol: MolFile, format: ModelFormat<any> | unde
const indexA = Column.ofIntArray(Column.mapToArray(bonds.atomIdxA, x => x - 1, Int32Array));
const indexB = Column.ofIntArray(Column.mapToArray(bonds.atomIdxB, x => x - 1, Int32Array));
const order = Column.asArrayColumn(bonds.order, Int32Array);
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order }, count: atoms.count });
const pairBonds = IndexPairBonds.fromData(
{ pairs: { indexA, indexB, order }, count: atoms.count },
{ maxDistance: Infinity }
);
IndexPairBonds.Provider.set(models.representative, pairBonds);
}

View File

@@ -113,7 +113,10 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
return BondType.Flag.Covalent;
}
}, Int8Array));
const pairBonds = IndexPairBonds.fromData({ pairs: { key, indexA, indexB, order, flag }, count: atoms.count });
const pairBonds = IndexPairBonds.fromData(
{ pairs: { key, indexA, indexB, order, flag }, count: atoms.count },
{ maxDistance: crysin ? -1 : Infinity }
);
const first = _models.representative;
IndexPairBonds.Provider.set(first, pairBonds);

View File

@@ -106,7 +106,7 @@ namespace CustomElementProperty {
factory: Coloring,
getParams: () => ({}),
defaultValues: {},
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -141,7 +141,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
Vec3.normalize(elements[iA].geometry.direction, elements[iA].geometry.direction);
}
const tmpV = Vec3.zero();
const tmpV = Vec3();
function fixTerminalLinkDirection(iA: number, indexB: number, unitB: Unit.Atomic) {
const pos = unitB.conformation.position, geo = elements[iA].geometry;
Vec3.sub(geo.direction, pos(unitB.elements[indexB], tmpV), geo.center);
@@ -189,9 +189,10 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
const anomericCarbon = getAnomericCarbon(unit, ringAtoms);
const ma = PrincipalAxes.calculateMomentsAxes(getPositions(unit, ringAtoms));
const center = Vec3.copy(Vec3.zero(), ma.origin);
const normal = Vec3.copy(Vec3.zero(), ma.dirC);
const direction = getDirection(Vec3.zero(), unit, anomericCarbon, center);
const a = PrincipalAxes.calculateNormalizedAxes(ma);
const center = Vec3.copy(Vec3(), a.origin);
const normal = Vec3.copy(Vec3(), a.dirC);
const direction = getDirection(Vec3(), unit, anomericCarbon, center);
Vec3.orthogonalize(direction, normal, direction);
const ringAltId = UnitRing.getAltId(unit, ringAtoms);

View File

@@ -386,16 +386,6 @@ const DefaultSaccharideCompIdMap = (function () {
map.set(charmm[j], saccharide);
}
}
const glycam = GlycamSaccharideNames[saccharide.abbr];
if (glycam) {
for (let j = 0, jl = glycam.length; j < jl; ++j) {
// On collision, use PDB name as default.
if (!map.has(glycam[j])) {
map.set(glycam[j], saccharide);
}
}
}
}
SaccharideNames.forEach(name => {
if (!map.has(name)) map.set(name, UnknownSaccharideComponent);

View File

@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
import { computeCarbohydrates } from './carbohydrates/compute';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { idFactory } from '../../../mol-util/id-factory';
import { GridLookup3D } from '../../../mol-math/geometry';
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
import { UUID } from '../../../mol-util';
import { CustomProperties } from '../../custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
@@ -43,6 +43,8 @@ type State = {
lookup3d?: StructureLookup3D,
interUnitBonds?: InterUnitBonds,
dynamicBonds: boolean,
interBondsValidUnit?: (unit: Unit) => boolean,
interBondsValidUnitPair?: (structure: Structure, unitA: Unit, unitB: Unit) => boolean,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
@@ -241,6 +243,8 @@ class Structure {
this.state.interUnitBonds = computeInterUnitBonds(this, {
ignoreWater: !this.dynamicBonds,
ignoreIon: !this.dynamicBonds,
validUnit: this.state.interBondsValidUnit,
validUnitPair: this.state.interBondsValidUnitPair,
});
}
return this.state.interUnitBonds;
@@ -250,6 +254,14 @@ class Structure {
return this.state.dynamicBonds;
}
get interBondsValidUnit() {
return this.state.interBondsValidUnit;
}
get interBondsValidUnitPair() {
return this.state.interBondsValidUnitPair;
}
get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups;
this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
@@ -380,7 +392,12 @@ class Structure {
parent: parent?.remapModel(m),
label: this.label,
interUnitBonds: dynamicBonds ? undefined : interUnitBonds,
dynamicBonds
dynamicBonds,
interBondsValidUnit: this.state.interBondsValidUnit,
interBondsValidUnitPair: this.state.interBondsValidUnitPair,
coordinateSystem: this.state.coordinateSystem,
masterModel: this.state.masterModel,
representativeModel: this.state.representativeModel,
});
}
@@ -428,7 +445,6 @@ class Structure {
function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
return units[i].id - units[j].id;
}
function getModels(s: Structure) {
@@ -634,6 +650,8 @@ namespace Structure {
* Also enables calculation of inter-unit bonds in water molecules.
*/
dynamicBonds?: boolean,
interBondsValidUnit?: (unit: Unit) => boolean,
interBondsValidUnitPair?: (structure: Structure, unitA: Unit, unitB: Unit) => boolean,
coordinateSystem?: SymmetryOperator
label?: string
/** Master model for structures of a protein model and multiple ligand models */
@@ -722,6 +740,12 @@ namespace Structure {
if (props.parent) state.parent = props.parent.parent || props.parent;
if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds;
if (props.interBondsValidUnit) state.interBondsValidUnit = props.interBondsValidUnit;
else if (props.parent) state.interBondsValidUnit = props.parent.interBondsValidUnit;
if (props.interBondsValidUnitPair) state.interBondsValidUnitPair = props.interBondsValidUnitPair;
else if (props.parent) state.interBondsValidUnitPair = props.parent.interBondsValidUnitPair;
if (props.dynamicBonds) state.dynamicBonds = props.dynamicBonds;
else if (props.parent) state.dynamicBonds = props.parent.dynamicBonds;
@@ -1180,7 +1204,7 @@ namespace Structure {
/**
* Iterate over all unit pairs of a structure and invokes callback for valid units
* and unit pairs if within a max distance.
* and unit pairs if their boundaries are within a max distance.
*/
export function eachUnitPair(structure: Structure, callback: (unitA: Unit, unitB: Unit) => void, props: EachUnitPairProps) {
const { maxRadius, validUnit, validUnitPair } = props;
@@ -1188,15 +1212,19 @@ namespace Structure {
const lookup = structure.lookup3d;
const imageCenter = Vec3();
const bbox = Box3D();
const rvec = Vec3.create(maxRadius, maxRadius, maxRadius);
for (const unit of structure.units) {
if (!validUnit(unit)) continue;
const bs = unit.boundary.sphere;
Box3D.expand(bbox, unit.boundary.box, rvec);
Vec3.transformMat4(imageCenter, bs.center, unit.conformation.operator.matrix);
const closeUnits = lookup.findUnitIndices(imageCenter[0], imageCenter[1], imageCenter[2], bs.radius + maxRadius);
for (let i = 0; i < closeUnits.count; i++) {
const other = structure.units[closeUnits.indices[i]];
if (!Box3D.overlaps(bbox, other.boundary.box)) continue;
if (!validUnit(other) || unit.id >= other.id || !validUnitPair(unit, other)) continue;
if (other.elements.length >= unit.elements.length) callback(unit, other);

View File

@@ -21,12 +21,18 @@ import { StructConn } from '../../../../../mol-model-formats/structure/property/
import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
import { Model } from '../../../model';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3distance = Vec3.distance;
const v3set = Vec3.set;
const v3squaredDistance = Vec3.squaredDistance;
const v3transformMat4 = Vec3.transformMat4;
const tmpDistVecA = Vec3();
const tmpDistVecB = Vec3();
function getDistance(unitA: Unit.Atomic, indexA: ElementIndex, unitB: Unit.Atomic, indexB: ElementIndex) {
unitA.conformation.position(indexA, tmpDistVecA);
unitB.conformation.position(indexB, tmpDistVecB);
return Vec3.distance(tmpDistVecA, tmpDistVecB);
return v3distance(tmpDistVecA, tmpDistVecB);
}
const _imageTransform = Mat4();
@@ -68,22 +74,22 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
const aI = atomsA[_aI];
Vec3.set(_imageA, xA[aI], yA[aI], zA[aI]);
if (isNotIdentity) Vec3.transformMat4(_imageA, _imageA, imageTransform);
if (Vec3.squaredDistance(_imageA, bCenter) > testDistanceSq) continue;
v3set(_imageA, xA[aI], yA[aI], zA[aI]);
if (isNotIdentity) v3transformMat4(_imageA, _imageA, imageTransform);
if (v3squaredDistance(_imageA, bCenter) > testDistanceSq) continue;
if (!props.forceCompute && indexPairs) {
const { maxDistance } = indexPairs;
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
const srcA = sourceIndex.value(aI);
const aeI = getElementIdx(type_symbolA.value(aI));
for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) {
const bI = invertedIndex![b[i]];
const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
const aeI = getElementIdx(type_symbolA.value(aI));
const beI = getElementIdx(type_symbolA.value(bI));
const d = distance[i];
@@ -191,6 +197,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
}
export interface InterBondComputationProps extends BondComputationProps {
validUnit: (unit: Unit) => boolean
validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
ignoreWater: boolean
ignoreIon: boolean
@@ -215,7 +222,7 @@ function findBonds(structure: Structure, props: InterBondComputationProps) {
findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder);
}, {
maxRadius: props.maxRadius,
validUnit: (unit: Unit) => Unit.isAtomic(unit),
validUnit: (unit: Unit) => props.validUnit(unit),
validUnitPair: (unitA: Unit, unitB: Unit) => props.validUnitPair(structure, unitA, unitB)
});
@@ -226,6 +233,7 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
const p = { ...DefaultInterBondComputationProps, ...props };
return findBonds(structure, {
...p,
validUnit: (props && props.validUnit) || (u => Unit.isAtomic(u)),
validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;

View File

@@ -21,6 +21,9 @@ import { ElementIndex } from '../../../model/indexing';
import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
import { Model } from '../../../model/model';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3distance = Vec3.distance;
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
const flags = new Uint16Array(builder.slotCount);
@@ -39,7 +42,7 @@ const tmpDistVecB = Vec3();
function getDistance(unit: Unit.Atomic, indexA: ElementIndex, indexB: ElementIndex) {
unit.conformation.position(indexA, tmpDistVecA);
unit.conformation.position(indexB, tmpDistVecB);
return Vec3.distance(tmpDistVecA, tmpDistVecB);
return v3distance(tmpDistVecA, tmpDistVecB);
}
const __structConnAdded = new Set<StructureElement.UnitIndex>();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -126,12 +126,12 @@ export namespace Volume {
'absolute': PD.Converted(
(v: Volume.IsoValue) => Volume.IsoValue.toAbsolute(v, Grid.One.stats).absoluteValue,
(v: number) => Volume.IsoValue.absolute(v),
PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) })
PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) }, { immediateUpdate: true })
),
'relative': PD.Converted(
(v: Volume.IsoValue) => Volume.IsoValue.toRelative(v, Grid.One.stats).relativeValue,
(v: number) => Volume.IsoValue.relative(v),
PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) })
PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) }, { immediateUpdate: true })
)
},
(v: Volume.IsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',

View File

@@ -83,7 +83,7 @@ export const DownloadFile = StateAction.build({
display: { name: 'Download File', description: 'Load one or more file from an URL' },
from: PluginStateObject.Root,
params: (a, ctx: PluginContext) => {
const options = [...ctx.dataFormats.options, ['zip', 'Zip'] as const];
const options = [...ctx.dataFormats.options, ['zip', 'Zip'] as const, ['gzip', 'Gzip'] as const];
return {
url: PD.Url(''),
format: PD.Select(options[0][0], options),
@@ -96,17 +96,23 @@ export const DownloadFile = StateAction.build({
await state.transaction(async () => {
try {
if (params.format === 'zip') {
if (params.format === 'zip' || params.format === 'gzip') {
// TODO: add ReadZipFile transformer so this can be saved as a simple state snaphot,
// would need support for extracting individual files from zip
const data = await plugin.builders.data.download({ url: params.url, isBinary: true });
const zippedFiles = await unzip(taskCtx, (data.obj?.data as Uint8Array).buffer);
for (const [fn, filedata] of Object.entries(zippedFiles)) {
if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue;
if (params.format === 'zip') {
const zippedFiles = await unzip(taskCtx, (data.obj?.data as Uint8Array).buffer);
for (const [fn, filedata] of Object.entries(zippedFiles)) {
if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue;
const asset = Asset.File(new File([filedata], fn));
const asset = Asset.File(new File([filedata], fn));
await processFile(asset, plugin, 'auto', params.visuals);
await processFile(asset, plugin, 'auto', params.visuals);
}
} else {
const url = Asset.getUrl(params.url);
const info = getFileInfo(url);
await processFile(Asset.File(new File([data.obj?.data as Uint8Array], info.name)), plugin, 'auto', params.visuals);
}
} else {
const provider = plugin.dataFormats.get(params.format);

View File

@@ -104,7 +104,7 @@ class Channel extends PluginUIComponent<{
colorStripe={channel.color}
pivot={<div className='msp-volume-channel-inline-controls'>
<Slider value={value} min={ctrlMin} max={ctrlMax} step={step}
onChange={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} />
onChange={v => props.changeIso(props.name, v, isRelative)} onChangeImmediate={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} />
<IconButton svg={this.getVisible() ? VisibilityOutlinedSvg : VisibilityOffOutlinedSvg} onClick={this.toggleVisible} toggleState={false} disabled={props.params.isDisabled} />
</div>}
controls={<ParameterControls onChange={({ name, value }) => props.changeParams(props.name, name, value)} params={ChannelParams} values={channel} onEnter={props.params.events.onEnter} isDisabled={props.params.isDisabled} />}

View File

@@ -8,8 +8,10 @@
import { produce } from 'immer';
import { Canvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { StateTransform } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { deepClone } from '../../mol-util/object';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ParamMapping } from '../../mol-util/param-mapping';
import { Mutable } from '../../mol-util/type-helpers';
@@ -50,7 +52,8 @@ const SimpleSettingsParams = {
camera: Canvas3DParams.camera,
background: PD.Group({
color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }),
transparent: PD.Boolean(false)
transparent: PD.Boolean(false),
style: Canvas3DParams.postprocessing.params.background,
}, { pivot: 'color' }),
lighting: PD.Group({
occlusion: Canvas3DParams.postprocessing.params.occlusion,
@@ -75,6 +78,13 @@ const SimpleSettingsMapping = ParamMapping({
if (controls.left !== 'none') options.push(['left', LayoutOptions.left]);
params.layout.options = options;
}
const bgStyles = ctx.config.get(PluginConfig.Background.Styles) || [];
if (bgStyles.length > 0) {
Object.assign(params.background.params.style, {
presets: deepClone(bgStyles),
isFlat: false, // so the presets menu is shown
});
}
return params;
},
target(ctx: PluginUIContext) {
@@ -97,7 +107,8 @@ const SimpleSettingsMapping = ParamMapping({
camera: canvas.camera,
background: {
color: renderer.backgroundColor,
transparent: canvas.transparentBackground
transparent: canvas.transparentBackground,
style: canvas.postprocessing.background,
},
lighting: {
occlusion: canvas.postprocessing.occlusion,
@@ -117,6 +128,7 @@ const SimpleSettingsMapping = ParamMapping({
canvas.renderer.backgroundColor = s.background.color;
canvas.postprocessing.occlusion = s.lighting.occlusion;
canvas.postprocessing.outline = s.lighting.outline;
canvas.postprocessing.background = s.background.style;
canvas.cameraFog = s.lighting.fog;
canvas.cameraClipping = {
radius: s.clipping.radius,

View File

@@ -17,7 +17,8 @@ export { PluginBehavior };
interface PluginBehavior<P = unknown> {
register(ref: StateTransform.Ref): void,
unregister(): void,
unregister?(): void,
dispose?(): void,
/** Update params in place. Optionally return a promise if it depends on an async action. */
update?(params: P): boolean | Promise<boolean>
@@ -102,7 +103,7 @@ namespace PluginBehavior {
register(): void {
this.sub = cmd.subscribe(this.ctx, data => action(data, this.ctx));
}
unregister(): void {
dispose(): void {
if (this.sub) this.sub.unsubscribe();
this.sub = void 0;
}
@@ -123,7 +124,7 @@ namespace PluginBehavior {
this.subs.push(sub);
}
abstract register(): void;
unregister() {
dispose(): void {
for (const s of this.subs) s.unsubscribe();
this.subs = [];
}
@@ -146,8 +147,7 @@ namespace PluginBehavior {
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void) {
this.subs.push(o.subscribe(action));
}
unregister() {
dispose(): void {
for (const s of this.subs) s.unsubscribe();
this.subs = [];
}

View File

@@ -37,12 +37,16 @@ export function SyncBehaviors(ctx: PluginContext) {
ctx.state.events.object.removed.subscribe(o => {
if (!SO.isBehavior(o.obj)) return;
o.obj.data.unregister();
o.obj.data.unregister?.();
o.obj.data.dispose?.();
});
ctx.state.events.object.updated.subscribe(o => {
if (o.action === 'recreate') {
if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister();
if (o.oldObj && SO.isBehavior(o.oldObj)) {
o.oldObj.data.unregister?.();
o.oldObj.data.dispose?.();
}
if (o.obj && SO.isBehavior(o.obj)) o.obj.data.register(o.ref);
}
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -12,6 +12,7 @@ import { EmdbDownloadProvider } from '../mol-plugin-state/actions/volume';
import { StructureRepresentationPresetProvider } from '../mol-plugin-state/builder/structure/representation-preset';
import { PluginFeatureDetection } from './features';
import { SaccharideCompIdMapType } from '../mol-model/structure/structure/carbohydrates/constants';
import { BackgroundProps } from '../mol-canvas3d/passes/background';
export class PluginConfigItem<T = any> {
toString() { return this.key; }
@@ -65,6 +66,9 @@ export const PluginConfig = {
DefaultRepresentationPreset: item<string>('structure.default-representation-preset', 'auto'),
DefaultRepresentationPresetParams: item<StructureRepresentationPresetProvider.CommonParams>('structure.default-representation-preset-params', { }),
SaccharideCompIdMapType: item<SaccharideCompIdMapType>('structure.saccharide-comp-id-map-type', 'default'),
},
Background: {
Styles: item<[BackgroundProps, string][]>('background.styles', []),
}
};

View File

@@ -201,7 +201,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, pickPadding, enableWboit, preferWebGl1 });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
}
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
this.canvas3dInit.next(true);

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export const PluginFeatureDetection = {
@@ -13,7 +14,7 @@ export const PluginFeatureDetection = {
const unpportedSafariVersions = [
'Version/15.1 Safari',
'Version/15.2 Safari',
'Version/15.3 Safari'
'Version/15.3 Safari',
];
if (unpportedSafariVersions.some(v => navigator.userAgent.indexOf(v) > 0)) {
return true;
@@ -29,9 +30,10 @@ export const PluginFeatureDetection = {
return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
},
get wboit() {
if (typeof navigator === 'undefined' || typeof window === 'undefined') return true;
return true; // for testing
// if (typeof navigator === 'undefined' || typeof window === 'undefined') return true;
// disable Wboit in Safari 15
return !/Version\/15.\d Safari/.test(navigator.userAgent);
// // disable Wboit in Safari 15
// return !/Version\/15.\d Safari/.test(navigator.userAgent);
}
};

View File

@@ -124,7 +124,8 @@ class PluginState extends PluginComponent {
dispose() {
this.behaviors.cells.forEach(cell => {
if (PluginBehavior.Behavior.is(cell.obj)) {
cell.obj.data.unregister();
cell.obj.data.unregister?.();
cell.obj.data.dispose?.();
}
});

View File

@@ -309,7 +309,9 @@ class ViewportScreenshotHelper extends PluginComponent {
if (width <= 0 || height <= 0) return;
await ctx.update('Rendering image...');
const imageData = this.imagePass.getImageData(width, height, viewport);
const pass = this.imagePass;
await pass.updateBackground();
const imageData = pass.getImageData(width, height, viewport);
await ctx.update('Encoding image...');
const canvas = this.canvas;

View File

@@ -28,6 +28,7 @@ import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/textur
import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { isTimingMode } from '../../../mol-util/debug';
import { ValueCell } from '../../../mol-util/value-cell';
const SharedParams = {
...GaussianDensityParams,
@@ -101,7 +102,12 @@ async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structu
(surface.meta.resolution as GaussianSurfaceMeta['resolution']) = resolution;
Mesh.transform(surface, transform);
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
if (ctx.webgl && !ctx.webgl.isWebGL2) {
Mesh.uniformTriangleGroup(surface);
ValueCell.updateIfChanged(surface.varyingGroup, false);
} else {
ValueCell.updateIfChanged(surface.varyingGroup, true);
}
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxRadius);
surface.setBoundingSphere(sphere);
@@ -162,7 +168,12 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure:
(surface.meta.resolution as GaussianSurfaceMeta['resolution']) = resolution;
Mesh.transform(surface, transform);
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
if (ctx.webgl && !ctx.webgl.isWebGL2) {
Mesh.uniformTriangleGroup(surface);
ValueCell.updateIfChanged(surface.varyingGroup, false);
} else {
ValueCell.updateIfChanged(surface.varyingGroup, true);
}
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, maxRadius);
surface.setBoundingSphere(sphere);
@@ -229,7 +240,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
const axisOrder = Vec3.create(0, 1, 2);
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
if (isTimingMode) ctx.webgl.timer.markEnd('createGaussianSurfaceTextureMesh');
const groupCount = unit.elements.length;
@@ -303,7 +314,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
const axisOrder = Vec3.create(0, 1, 2);
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
if (isTimingMode) ctx.webgl.timer.markEnd('createStructureGaussianSurfaceTextureMesh');
const groupCount = structure.elementCount;

View File

@@ -22,6 +22,7 @@ import { Texture } from '../../../mol-gl/webgl/texture';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
import { ValueCell } from '../../../mol-util';
export const MolecularSurfaceMeshParams = {
...UnitsMeshParams,
@@ -55,7 +56,12 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
}
Mesh.transform(surface, transform);
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
if (ctx.webgl && !ctx.webgl.isWebGL2) {
Mesh.uniformTriangleGroup(surface);
ValueCell.updateIfChanged(surface.varyingGroup, false);
} else {
ValueCell.updateIfChanged(surface.varyingGroup, true);
}
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxRadius);
surface.setBoundingSphere(sphere);

View File

@@ -29,6 +29,7 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
import { Texture } from '../../mol-gl/webgl/texture';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { ValueCell } from '../../mol-util/value-cell';
export const VolumeIsosurfaceParams = {
isoValue: Volume.IsoValueParam
@@ -94,6 +95,9 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol
// 2nd arg means not to split triangles based on group id. Splitting triangles
// is too expensive if each cell has its own group id as is the case here.
Mesh.uniformTriangleGroup(surface, false);
ValueCell.updateIfChanged(surface.varyingGroup, false);
} else {
ValueCell.updateIfChanged(surface.varyingGroup, true);
}
surface.setBoundingSphere(Volume.getBoundingSphere(volume));
@@ -185,7 +189,7 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
const groupCount = volume.grid.cells.data.length;
const surface = TextureMesh.create(gv.vertexCount, groupCount, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -22,6 +22,7 @@ import { Representation } from '../../mol-repr/representation';
import { computeMarchingCubesMesh } from '../../mol-geo/util/marching-cubes/algorithm';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { AssetManager } from '../../mol-util/assets';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -31,7 +32,9 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas), PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), {
const assetManager = new AssetManager();
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager), PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), {
renderer: { backgroundColor: ColorNames.white },
camera: { mode: 'orthographic' }
}));
@@ -73,7 +76,7 @@ async function init() {
console.timeEnd('gpu mc pyramid2');
console.time('gpu mc vert2');
createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true, Vec3.create(0, 1, 2));
createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true, Vec3.create(0, 1, 2), true);
webgl.waitForGpuCommandsCompleteSync();
console.timeEnd('gpu mc vert2');
console.timeEnd('gpu mc2');
@@ -96,7 +99,7 @@ async function init() {
console.timeEnd('gpu mc pyramid');
console.time('gpu mc vert');
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2));
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2), true);
webgl.waitForGpuCommandsCompleteSync();
console.timeEnd('gpu mc vert');
console.timeEnd('gpu mc');

View File

@@ -15,6 +15,7 @@ import { Color } from '../../mol-util/color';
import { createRenderObject } from '../../mol-gl/render-object';
import { Representation } from '../../mol-repr/representation';
import { ParamDefinition } from '../../mol-util/param-definition';
import { AssetManager } from '../../mol-util/assets';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -24,7 +25,9 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
const assetManager = new AssetManager();
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager));
canvas3d.animate();
function linesRepr() {

View File

@@ -17,6 +17,7 @@ import { createRenderObject } from '../../mol-gl/render-object';
import { Representation } from '../../mol-repr/representation';
import { Torus } from '../../mol-geo/primitive/torus';
import { ParamDefinition } from '../../mol-util/param-definition';
import { AssetManager } from '../../mol-util/assets';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -26,7 +27,9 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
const assetManager = new AssetManager();
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager));
canvas3d.animate();
function meshRepr() {

View File

@@ -19,6 +19,7 @@ import { Sphere } from '../../mol-geo/primitive/sphere';
import { ColorNames } from '../../mol-util/color/names';
import { Shape } from '../../mol-model/shape';
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { AssetManager } from '../../mol-util/assets';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -28,6 +29,8 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const assetManager = new AssetManager();
const info = document.createElement('div');
info.style.position = 'absolute';
info.style.fontFamily = 'sans-serif';
@@ -38,7 +41,7 @@ info.style.color = 'white';
parent.appendChild(info);
let prevReprLoci = Representation.Loci.Empty;
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager));
canvas3d.animate();
canvas3d.input.move.subscribe(({ x, y }) => {
const pickingId = canvas3d.identify(x, y)?.id;

View File

@@ -13,6 +13,7 @@ import { Color } from '../../mol-util/color';
import { createRenderObject } from '../../mol-gl/render-object';
import { Representation } from '../../mol-repr/representation';
import { ParamDefinition } from '../../mol-util/param-definition';
import { AssetManager } from '../../mol-util/assets';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -22,7 +23,9 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
const assetManager = new AssetManager();
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager));
canvas3d.animate();
function spheresRepr() {

View File

@@ -37,7 +37,9 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
const assetManager = new AssetManager();
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager));
canvas3d.animate();
const info = document.createElement('div');
@@ -123,7 +125,7 @@ function getMembraneOrientationRepr() {
}
async function init() {
const ctx = { runtime: SyncRuntimeContext, assetManager: new AssetManager() };
const ctx = { runtime: SyncRuntimeContext, assetManager };
const cif = await downloadFromPdb('3pqr');
const models = await getModels(cif);

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