Compare commits
68 Commits
v3.12.1
...
safari-deb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db0ada684 | ||
|
|
e474e9b090 | ||
|
|
837f9a6c74 | ||
|
|
c357aed7bb | ||
|
|
59ffddfd8d | ||
|
|
fb3accaa36 | ||
|
|
b3e79544ad | ||
|
|
2ee0f3bf97 | ||
|
|
a56b5edc4e | ||
|
|
f2d71b6551 | ||
|
|
ef560ddc03 | ||
|
|
2e30ffe1bc | ||
|
|
325b5e9297 | ||
|
|
ae9e04b8d4 | ||
|
|
ab0010122b | ||
|
|
08d736ecdc | ||
|
|
9c362c8ffd | ||
|
|
62c8778560 | ||
|
|
2fe0665e12 | ||
|
|
14a957f517 | ||
|
|
087010d0a1 | ||
|
|
f92657310a | ||
|
|
19e91400b5 | ||
|
|
7885fb7b4f | ||
|
|
331bec11ee | ||
|
|
f219cd6c8b | ||
|
|
e697624064 | ||
|
|
92ffdeb5bf | ||
|
|
ddefe7e542 | ||
|
|
fb4019c041 | ||
|
|
46026e047e | ||
|
|
0dfad5a757 | ||
|
|
a0495f8aae | ||
|
|
1610f05b83 | ||
|
|
8202b75cda | ||
|
|
4904bae5a6 | ||
|
|
04c06db02c | ||
|
|
a96f94b676 | ||
|
|
ebdfc694c2 | ||
|
|
7f29340797 | ||
|
|
113d0b5141 | ||
|
|
163285b0a9 | ||
|
|
9f1cf5377a | ||
|
|
c37636215b | ||
|
|
1f77b19ced | ||
|
|
9853ebf02f | ||
|
|
6e13aa0bc9 | ||
|
|
1b7f0e0f1e | ||
|
|
18cb3360b5 | ||
|
|
6fec598b96 | ||
|
|
40096ecdfb | ||
|
|
43061b80b8 | ||
|
|
aa3d657d42 | ||
|
|
b0ef385769 | ||
|
|
dcf24e6292 | ||
|
|
2fdd77737c | ||
|
|
31c98ef1ba | ||
|
|
ceeec2c13a | ||
|
|
cc82e0cff8 | ||
|
|
29fc6c59e9 | ||
|
|
aa931fab7b | ||
|
|
8e2585a5c0 | ||
|
|
c115047f74 | ||
|
|
0ac58cb137 | ||
|
|
492e0977c3 | ||
|
|
e8a09e81f3 | ||
|
|
4fcc2c6208 | ||
|
|
e3523dc5fe |
41
CHANGELOG.md
@@ -6,6 +6,47 @@ 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
1694
examples/1bna_confal_pyramids.cif
Normal file
1987
package-lock.json
generated
50
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.12.1",
|
||||
"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"
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
BIN
src/extensions/backgrounds/images/cells.jpg
Normal file
|
After Width: | Height: | Size: 181 KiB |
90
src/extensions/backgrounds/index.ts
Normal 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: () => ({ })
|
||||
});
|
||||
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_back6.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_bottom4.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_front5.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_left2.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_right1.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_top3.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
10
src/extensions/backgrounds/typings.d.ts
vendored
Normal 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;
|
||||
}
|
||||
@@ -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)}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
461
src/mol-canvas3d/passes/background.ts
Normal 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);
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
85
src/mol-gl/shader/background.frag.ts
Normal 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
|
||||
}
|
||||
`;
|
||||
12
src/mol-gl/shader/background.vert.ts
Normal 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);
|
||||
}
|
||||
`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', []),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
|
||||
import { createRenderObject } from '../../mol-gl/render-object';
|
||||
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
|
||||
import { resizeCanvas } from '../../mol-canvas3d/util';
|
||||
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 textRepr() {
|
||||
|
||||
@@ -30,7 +30,11 @@ const sharedConfig = {
|
||||
{ loader: 'css-loader', options: { sourceMap: false } },
|
||||
{ loader: 'sass-loader', options: { sourceMap: false } },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(jpg)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
@@ -76,7 +80,7 @@ function createEntry(src, outFolder, outFilename, isNode) {
|
||||
function createEntryPoint(name, dir, out, library) {
|
||||
return {
|
||||
entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
|
||||
output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd' },
|
||||
output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd', assetModuleFilename: 'images/[hash][ext][query]', 'publicPath': '' },
|
||||
...sharedConfig
|
||||
};
|
||||
}
|
||||
|
||||