mirror of
https://github.com/molstar/molstar.git
synced 2026-06-08 07:54:28 +08:00
Compare commits
173 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab34a59677 | ||
|
|
7a96cdd52d | ||
|
|
65cad5ea4d | ||
|
|
a765ba8e3b | ||
|
|
8594ce80a9 | ||
|
|
915797c4a4 | ||
|
|
44c69f538b | ||
|
|
e4396039fd | ||
|
|
e548a3ed85 | ||
|
|
fc44e66b26 | ||
|
|
98f3f5a23b | ||
|
|
f2f10d0cb5 | ||
|
|
aed1056d6c | ||
|
|
be47ac09c9 | ||
|
|
d5e7797a40 | ||
|
|
0aeac628c7 | ||
|
|
668d617cd7 | ||
|
|
62ed993f0d | ||
|
|
aa0a008a41 | ||
|
|
89f01f202d | ||
|
|
733190f7a0 | ||
|
|
50429aacfa | ||
|
|
fa541bdbd3 | ||
|
|
77d173afed | ||
|
|
a934001ae8 | ||
|
|
e5d4606437 | ||
|
|
fb16cd0070 | ||
|
|
c427549b8d | ||
|
|
310300bde8 | ||
|
|
11604b9e8f | ||
|
|
cc1bf482f2 | ||
|
|
61a351b3d4 | ||
|
|
9e91a242bf | ||
|
|
c3daa1a162 | ||
|
|
fe086fb62e | ||
|
|
c2217829a3 | ||
|
|
6333c8073f | ||
|
|
2801bcf111 | ||
|
|
8a2461e157 | ||
|
|
0a081e2a8a | ||
|
|
700a3fe95c | ||
|
|
febc634d8b | ||
|
|
0105f75bb6 | ||
|
|
4cc2073eaa | ||
|
|
9ac204cb6e | ||
|
|
73378bbe9d | ||
|
|
9b5fd2595c | ||
|
|
bca2073ed0 | ||
|
|
f264e4d6b8 | ||
|
|
795222b5b4 | ||
|
|
25eb4450ad | ||
|
|
140df13dae | ||
|
|
792cd513a8 | ||
|
|
14e6172c33 | ||
|
|
911433e056 | ||
|
|
6b585cf0d6 | ||
|
|
86211aaf3a | ||
|
|
071623f5b6 | ||
|
|
21e514ec1e | ||
|
|
9bc0ab12e7 | ||
|
|
1d1bd05400 | ||
|
|
faa750bbf9 | ||
|
|
e92e5c5cef | ||
|
|
b49230ea1f | ||
|
|
44ebc1d39a | ||
|
|
8d8e45f4ce | ||
|
|
898d877aa1 | ||
|
|
85dba9b1a4 | ||
|
|
6b5e90c5fa | ||
|
|
e231fbf3d7 | ||
|
|
0ee8525b2d | ||
|
|
106ee614e7 | ||
|
|
34056751f9 | ||
|
|
1afea8a86a | ||
|
|
96d5bf2447 | ||
|
|
f9265a7049 | ||
|
|
5c57137890 | ||
|
|
4e71618d0f | ||
|
|
de660cc233 | ||
|
|
616a1dabfa | ||
|
|
46ea39703f | ||
|
|
6cf20d0c44 | ||
|
|
0737e23b70 | ||
|
|
70d0c15d28 | ||
|
|
9272c8c5ec | ||
|
|
a3349f82fc | ||
|
|
4d399edbdd | ||
|
|
64598eba96 | ||
|
|
aa25874775 | ||
|
|
dccc06d497 | ||
|
|
c000526cf8 | ||
|
|
2166ab455c | ||
|
|
4de9ce01fc | ||
|
|
f543fd5683 | ||
|
|
8535013ee5 | ||
|
|
320ab77f8e | ||
|
|
982feef0c6 | ||
|
|
bd6d04cefb | ||
|
|
5e1c351efc | ||
|
|
61a294c889 | ||
|
|
71fbd6baab | ||
|
|
33430a836a | ||
|
|
f428e9f39e | ||
|
|
2d26425cbe | ||
|
|
f6030aee25 | ||
|
|
609e03f7d2 | ||
|
|
ba12a8bbee | ||
|
|
947f293844 | ||
|
|
fbff0e769c | ||
|
|
3798223d39 | ||
|
|
ac9c23dc65 | ||
|
|
096f492ccb | ||
|
|
ba96da9354 | ||
|
|
6c1d17bac5 | ||
|
|
ad2ccf4e07 | ||
|
|
dc1b7b4693 | ||
|
|
59e4e2b31d | ||
|
|
d2483dc449 | ||
|
|
d26946e9ee | ||
|
|
cd045a6b48 | ||
|
|
2407729d27 | ||
|
|
1aa22b9fa0 | ||
|
|
35c9f39a69 | ||
|
|
7dd420cc18 | ||
|
|
1d434c259a | ||
|
|
6d193edd68 | ||
|
|
9bf859d6ed | ||
|
|
207230d565 | ||
|
|
b7a673f38e | ||
|
|
2204e4e0d0 | ||
|
|
6276365766 | ||
|
|
505b04c92d | ||
|
|
fc84dcb037 | ||
|
|
2f29ff7314 | ||
|
|
b37f043876 | ||
|
|
f0e725f65c | ||
|
|
23a34e2df1 | ||
|
|
d11e242b70 | ||
|
|
d9af0ca068 | ||
|
|
b7f10acbf0 | ||
|
|
43749ccdbd | ||
|
|
3bf4a8f8e6 | ||
|
|
f0ae1b3347 | ||
|
|
99809d25b9 | ||
|
|
e83c0af67c | ||
|
|
2ddf94313e | ||
|
|
da5965c956 | ||
|
|
31be0af3c9 | ||
|
|
38c550b245 | ||
|
|
95a7a2cef9 | ||
|
|
1a1ec51736 | ||
|
|
299aae56c1 | ||
|
|
781824c961 | ||
|
|
930cfa2590 | ||
|
|
35439f01aa | ||
|
|
3d96298b55 | ||
|
|
964f045e56 | ||
|
|
d3364ac109 | ||
|
|
a5b963c919 | ||
|
|
22f9bc4ff1 | ||
|
|
c6c4350638 | ||
|
|
1b0401dff5 | ||
|
|
0295e0ef63 | ||
|
|
6e82405600 | ||
|
|
a678893bdb | ||
|
|
66b4fcdc2c | ||
|
|
1f3e20704d | ||
|
|
cc9bdd4f14 | ||
|
|
fbc74c0012 | ||
|
|
27a953795c | ||
|
|
6ada52bc0b | ||
|
|
c526cb9f08 | ||
|
|
a1662d76fb |
94
CHANGELOG.md
94
CHANGELOG.md
@@ -6,6 +6,100 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [v3.41.0] - 2023-10-15
|
||||
|
||||
- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
|
||||
- Fix undesired interaction between settings panel and the panel on the right.
|
||||
- Add ability to customize server parameters for `RCSBAssemblySymmetry`.
|
||||
|
||||
## [v3.40.1] - 2023-09-30
|
||||
|
||||
- Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.
|
||||
- Treat "tap" as a click in `InputObserver`
|
||||
- ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
|
||||
- CCD extension: Make visuals for aromatic bonds configurable
|
||||
- Add optional `file?: CifFile` to `MmcifFormat.data`
|
||||
- Add support for webgl extensions
|
||||
- `WEBGL_clip_cull_distance`
|
||||
- `EXT_conservative_depth`
|
||||
- `WEBGL_stencil_texturing`
|
||||
- `EXT_clip_control`
|
||||
- Add `MultiSampleParams.reduceFlicker` (to be able to switch it off)
|
||||
- Add `alphaThickness` parameter to adjust alpha of spheres for radius
|
||||
- Ability to hide "right" panel from simplified viewport controls
|
||||
- Add `blockIndex` parameter to TrajectoryFromMmCif
|
||||
- Fix bounding sphere calculation for "element-like" visuals
|
||||
- Fix RCSB PDB validation report URL
|
||||
- Add sharpening postprocessing option
|
||||
- Take pixel-ratio into account for outline scale
|
||||
- Gracefully handle missing HTMLImageElement
|
||||
- Fix pixel-ratio changes not applied to all render passes
|
||||
|
||||
## [v3.39.0] - 2023-09-02
|
||||
|
||||
- Add some elements support for `guessElementSymbolString` function
|
||||
- Faster bounding rectangle calculation for imposter spheres
|
||||
- Allow toggling of hydrogens as part of `LabelTextVisual`
|
||||
|
||||
## [v3.38.3] - 2023-07-29
|
||||
|
||||
- Fix imposter spheres not updating, e.g. in trajectories (broke in v3.38.0)
|
||||
|
||||
## [v3.38.2] - 2023-07-24
|
||||
|
||||
- Don't rely solely on `chem_comp_atom` when detecting CCD files (#877)
|
||||
- Actually support non-physical keys in `Bindings.Trigger.code`
|
||||
|
||||
## [v3.38.1] - 2023-07-22
|
||||
|
||||
- Fix pixel-scale not updated in SSAO pass
|
||||
|
||||
## [v3.38.0] - 2023-07-18
|
||||
|
||||
- Fix display issue with SIFTS mapping
|
||||
- Support non-physical keys in `Bindings.Trigger.code`
|
||||
- Update `getStateSnapshot` to only overwrite current snapshot if it was created automatically
|
||||
- Fix distinct palette's `getSamples` infinite loop
|
||||
- Add 'NH2', 'FOR', 'FMT' to `CommonProteinCaps`
|
||||
- Add `opened` event to `PluginStateSnapshotManager`
|
||||
- Properly switch-off fog
|
||||
- Add `approximate` option for spheres rendering
|
||||
- Reduce `Spheres` memory usage
|
||||
- Derive mapping from VertexID
|
||||
- Pull position and group from texture
|
||||
- Add `Euler` math primitive
|
||||
- Add stride option to element sphere & point visuals
|
||||
- Add `disabledExtensions` field to default viewer's options
|
||||
- Add `LRUCache.remove`
|
||||
- Add 'Chain Instance' and 'Uniform' options for 'Carbon Color' param (in Color Theme: Element Symbol)
|
||||
|
||||
## [v3.37.1] - 2023-06-20
|
||||
|
||||
- Fix issues with wboit/dpoit in large scenes
|
||||
- Fix lines, text, points rendering (broken in v3.37.0)
|
||||
|
||||
## [v3.37.0] - 2023-06-17
|
||||
|
||||
- Add `inverted` option to `xrayShaded` parameter
|
||||
- Model-export extension: Add ability to set a file name for structures
|
||||
- Add `contextHash` to `SizeTheme`
|
||||
- Add mipmap-based blur for image backgrounds
|
||||
|
||||
## [v3.36.1] - 2023-06-11
|
||||
|
||||
- Allow parsing of CCD ligand files
|
||||
- Add dedicated wwPDB CCD extension to align and visualize ideal & model CCD coordinates
|
||||
- Make operators in `IndexPairBonds` a directed property
|
||||
- Remove erroneous bounding-box overlap test in `Structure.eachUnitPair`
|
||||
- Fix `EdgeBuilder.addNextEdge` for loop edges
|
||||
- Optimize inter unit bond compute
|
||||
- Ensure consistent state for volume representation (#210)
|
||||
- Improve SSAO for thin geometry (e.g. lines)
|
||||
- Add snapshot support for structure selections
|
||||
- Add `nucleicProfile` parameter to cartoon representation
|
||||
- Add `cartoon` theme with separate colorings for for mainchain and sidechain visuals
|
||||
|
||||
## [v3.35.0] - 2023-05-14
|
||||
|
||||
- Enable odd dash count (1,3,5)
|
||||
|
||||
6675
package-lock.json
generated
6675
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
84
package.json
84
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.35.0",
|
||||
"version": "3.41.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -103,57 +103,57 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^4.0.1",
|
||||
"@graphql-codegen/cli": "^3.3.1",
|
||||
"@graphql-codegen/time": "^4.0.0",
|
||||
"@graphql-codegen/typescript": "^3.0.4",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.9",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/gl": "^6.0.2",
|
||||
"@graphql-codegen/add": "^5.0.0",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/time": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^3.0.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^6.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^4.0.1",
|
||||
"@types/cors": "^2.8.14",
|
||||
"@types/gl": "^6.0.3",
|
||||
"@types/jpeg-js": "^0.3.7",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||
"@typescript-eslint/parser": "^5.59.5",
|
||||
"@types/pngjs": "^6.0.2",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/react": "^18.2.23",
|
||||
"@types/react-dom": "^18.2.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||
"@typescript-eslint/parser": "^6.7.3",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^8.0.1",
|
||||
"cpx2": "^4.2.3",
|
||||
"concurrently": "^8.2.1",
|
||||
"cpx2": "^5.0.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"eslint": "^8.40.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"eslint": "^8.50.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql": "^16.8.1",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^29.5.0",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"jest": "^29.7.0",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.2.2",
|
||||
"simple-git": "^3.18.0",
|
||||
"sass": "^1.68.0",
|
||||
"sass-loader": "^13.3.2",
|
||||
"simple-git": "^3.20.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.2",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.4",
|
||||
"webpack": "^5.82.1",
|
||||
"webpack-cli": "^5.1.1"
|
||||
"style-loader": "^3.3.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.2",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/node": "^16.18.30",
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"@types/argparse": "^2.0.11",
|
||||
"@types/benchmark": "^2.1.3",
|
||||
"@types/compression": "1.7.3",
|
||||
"@types/express": "^4.17.18",
|
||||
"@types/node": "^16.18.55",
|
||||
"@types/node-fetch": "^2.6.6",
|
||||
"@types/swagger-ui-dist": "3.30.2",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"compression": "^1.7.4",
|
||||
@@ -161,11 +161,11 @@
|
||||
"express": "^4.18.2",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.21",
|
||||
"immutable": "^4.3.0",
|
||||
"node-fetch": "^2.6.11",
|
||||
"immutable": "^4.3.4",
|
||||
"node-fetch": "^2.7.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-dist": "^4.18.3",
|
||||
"tslib": "^2.5.0",
|
||||
"swagger-ui-dist": "^5.9.0",
|
||||
"tslib": "^2.6.2",
|
||||
"util.promisify": "^1.1.2",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
|
||||
@@ -50,6 +50,8 @@ import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/car
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
|
||||
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
|
||||
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
|
||||
import { RCSBAssemblySymmetryConfig } from '../../extensions/rcsb/assembly-symmetry/behavior';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
|
||||
@@ -58,7 +60,7 @@ const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
const Extensions = {
|
||||
export const ExtensionMap = {
|
||||
'volseg': PluginSpec.Behavior(Volseg),
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
@@ -74,11 +76,13 @@ const Extensions = {
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
|
||||
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
extensions: ObjectKeys(ExtensionMap),
|
||||
disabledExtensions: [] as string[],
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
@@ -111,6 +115,9 @@ const DefaultViewerOptions = {
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerType: RCSBAssemblySymmetryConfig.DefaultServerType.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerUrl: RCSBAssemblySymmetryConfig.DefaultServerUrl.defaultValue,
|
||||
rcsbAssemblySymmetryApplyColors: RCSBAssemblySymmetryConfig.ApplyColors.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
@@ -129,11 +136,13 @@ export class Viewer {
|
||||
const o: ViewerOptions = { ...DefaultViewerOptions, ...definedOptions };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const disabledExtension = new Set(o.disabledExtensions ?? []);
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
...o.extensions.filter(e => !disabledExtension.has(e)).map(e => ExtensionMap[e]),
|
||||
],
|
||||
animations: [...defaultSpec.animations || []],
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
@@ -186,6 +195,9 @@ export class Viewer {
|
||||
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
|
||||
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
|
||||
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
|
||||
[RCSBAssemblySymmetryConfig.DefaultServerType, o.rcsbAssemblySymmetryDefaultServerType],
|
||||
[RCSBAssemblySymmetryConfig.DefaultServerUrl, o.rcsbAssemblySymmetryDefaultServerUrl],
|
||||
[RCSBAssemblySymmetryConfig.ApplyColors, o.rcsbAssemblySymmetryApplyColors],
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,10 @@
|
||||
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
|
||||
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
|
||||
|
||||
// console.log('Available extensions: ', Object.keys(molstar.ExtensionMap));
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
disabledExtensions: [], // anything from Object.keys(molstar.ExtensionMap)
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
collapseLeftPanel: collapseLeftPanel,
|
||||
|
||||
@@ -81,5 +81,5 @@ export const DefaultDataOptions: DataOptions = {
|
||||
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');
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
|
||||
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
|
||||
const CCD_URL = 'https://files.wwpdb.org/pub/pdb/data/monomers/components.cif';
|
||||
const PVCD_URL = 'https://files.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
|
||||
|
||||
@@ -22,6 +22,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'uline':
|
||||
case 'uchar3':
|
||||
case 'uchar1':
|
||||
case 'uchar5':
|
||||
// only force lower-case for enums
|
||||
return values && values.length ? EnumCol(values.map(x => x.toLowerCase()), 'lstr', description) : StrCol(description);
|
||||
case 'aliasname':
|
||||
@@ -61,6 +62,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'symop':
|
||||
case 'exp_data_doi':
|
||||
case 'asym_id':
|
||||
case 'uniprot_ptm_id':
|
||||
return StrCol(description);
|
||||
case 'int':
|
||||
case 'non_negative_int':
|
||||
@@ -89,6 +91,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'Tag':
|
||||
case 'Implied':
|
||||
case 'Word':
|
||||
case 'Uri':
|
||||
return wrapContainer('str', ',', description, container);
|
||||
case 'Real':
|
||||
return wrapContainer('float', ',', description, container);
|
||||
|
||||
@@ -50,7 +50,7 @@ const _testBasis: Basis = {
|
||||
0.025886090588624934,
|
||||
0.019164790004065606,
|
||||
-0.013539970104105408
|
||||
] as Vec3,
|
||||
],
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
@@ -101,7 +101,7 @@ const _testBasis: Basis = {
|
||||
0.5082729578468134,
|
||||
1.6880351220025265,
|
||||
0.4963443067810461
|
||||
] as Vec3,
|
||||
],
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
@@ -158,7 +158,7 @@ const _testBasis: Basis = {
|
||||
1.1367367844436005,
|
||||
-0.47018519422670163,
|
||||
-1.356802622574504
|
||||
] as Vec3,
|
||||
],
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function sphericalCollocation(
|
||||
L,
|
||||
shell.coefficients[amIndex++],
|
||||
shell.exponents,
|
||||
atom.center,
|
||||
atom.center as unknown as Vec3,
|
||||
cutoffThreshold,
|
||||
alpha
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface SphericalElectronShell {
|
||||
export interface Basis {
|
||||
atoms: {
|
||||
// in Bohr units!
|
||||
center: Vec3;
|
||||
center: [number, number, number];
|
||||
shells: SphericalElectronShell[];
|
||||
}[];
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
|
||||
const count = geometry.length;
|
||||
const box = Box3D.expand(
|
||||
Box3D(),
|
||||
Box3D.fromVec3Array(Box3D(), geometry),
|
||||
Box3D.fromVec3Array(Box3D(), geometry as unknown as Vec3[]),
|
||||
Vec3.create(expand, expand, expand)
|
||||
);
|
||||
const size = Box3D.size(Vec3(), box);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -28,6 +28,12 @@ export const Backgrounds = PluginBehavior.create<{ }>({
|
||||
ctor: class extends PluginBehavior.Handler<{ }> {
|
||||
register(): void {
|
||||
this.ctx.config.set(PluginConfig.Background.Styles, [
|
||||
[{
|
||||
variant: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
}
|
||||
}, 'Off'],
|
||||
[{
|
||||
variant: {
|
||||
name: 'radialGradient',
|
||||
@@ -50,6 +56,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
|
||||
lightness: 0,
|
||||
saturation: 0,
|
||||
opacity: 1,
|
||||
blur: 0,
|
||||
coverage: 'viewport',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,13 +465,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
const center = Vec3();
|
||||
|
||||
const aPosition = values.aPosition.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const aPosition = values.centerBuffer.ref.value;
|
||||
const aGroup = values.groupBuffer.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
const meshes: Mesh[] = [];
|
||||
|
||||
const sphereCount = vertexCount / 4 * instanceCount;
|
||||
const sphereCount = vertexCount / 6 * instanceCount;
|
||||
let detail: number;
|
||||
switch (this.options.primitivesQuality) {
|
||||
case 'auto':
|
||||
@@ -495,7 +495,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
const state = MeshBuilder.createState(512, 256);
|
||||
|
||||
for (let i = 0; i < vertexCount; i += 4) {
|
||||
for (let i = 0; i < sphereCount; ++i) {
|
||||
v3fromArray(center, aPosition, i * 3);
|
||||
|
||||
const group = aGroup[i];
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021-2023 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>
|
||||
*/
|
||||
|
||||
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
|
||||
import { to_mmCIF, Unit } from '../../mol-model/structure';
|
||||
import { Structure, to_mmCIF, Unit } from '../../mol-model/structure';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
|
||||
const ModelExportNameProp = '__ModelExportName__';
|
||||
export const ModelExport = {
|
||||
getStructureName(structure: Structure): string | undefined {
|
||||
return structure.inheritedPropertyData[ModelExportNameProp];
|
||||
},
|
||||
setStructureName(structure: Structure, name: string) {
|
||||
return structure.inheritedPropertyData[ModelExportNameProp] = name;
|
||||
}
|
||||
};
|
||||
|
||||
export async function exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
|
||||
try {
|
||||
await plugin.runTask(_exportHierarchy(plugin, options), { useOverlay: true });
|
||||
@@ -43,19 +54,21 @@ function _exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'b
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = entryMap.has(s.model.entryId)
|
||||
? `${s.model.entryId}_${entryMap.get(s.model.entryId)! + 1}.${format}`
|
||||
: `${s.model.entryId}.${format}`;
|
||||
entryMap.set(s.model.entryId, (entryMap.get(s.model.entryId) ?? 0) + 1);
|
||||
const name = ModelExport.getStructureName(s) || s.model.entryId || 'unnamed';
|
||||
|
||||
await ctx.update({ message: `Exporting ${s.model.entryId}...`, isIndeterminate: true, canAbort: false });
|
||||
const fileName = entryMap.has(name)
|
||||
? `${name}_${entryMap.get(name)! + 1}.${format}`
|
||||
: `${name}.${format}`;
|
||||
entryMap.set(name, (entryMap.get(name) ?? 0) + 1);
|
||||
|
||||
await ctx.update({ message: `Exporting ${name}...`, isIndeterminate: true, canAbort: false });
|
||||
if (s.elementCount > 100000) {
|
||||
// Give UI chance to update, only needed for larger structures.
|
||||
await new Promise(res => setTimeout(res, 50));
|
||||
}
|
||||
|
||||
try {
|
||||
files.push([name, to_mmCIF(s.model.entryId, s, format === 'bcif', { copyAllCategories: true })]);
|
||||
files.push([fileName, to_mmCIF(name, s, format === 'bcif', { copyAllCategories: true })]);
|
||||
} catch (e) {
|
||||
if (format === 'cif' && s.elementCount > 2000000) {
|
||||
plugin.log.warn(`[Export] The structure might be too big to be exported as Text CIF, consider using the BinaryCIF format instead.`);
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider, AssemblySymmetryDataParams } from './prop';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
|
||||
import { AssemblySymmetryClusterColorThemeProvider } from './color';
|
||||
import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { PluginConfigItem } from '../../../mol-plugin/config';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
|
||||
import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
@@ -77,14 +78,15 @@ export const InitAssemblySymmetry3D = StateAction.build({
|
||||
description: 'Initialize Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
|
||||
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data),
|
||||
params: (a, plugin: PluginContext) => getConfiguredDefaultParams(plugin)
|
||||
})(({ a, ref, state, params }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
|
||||
try {
|
||||
const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, a.data);
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, a.data, params);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { ...params, symmetryIndex });
|
||||
} catch (e) {
|
||||
plugin.log.error(`Assembly Symmetry: ${e}`);
|
||||
return;
|
||||
@@ -152,10 +154,6 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
|
||||
//
|
||||
|
||||
export const AssemblySymmetryPresetParams = {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
};
|
||||
|
||||
export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-assembly-symmetry',
|
||||
display: {
|
||||
@@ -165,7 +163,12 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
},
|
||||
params: () => AssemblySymmetryPresetParams,
|
||||
params: (a, plugin) => {
|
||||
return {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
...getConfiguredDefaultParams(plugin)
|
||||
};
|
||||
},
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
@@ -174,15 +177,16 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
if (!AssemblySymmetryDataProvider.get(structure).value) {
|
||||
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
|
||||
const propCtx = { runtime, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, structure);
|
||||
const propProps = { serverType: params.serverType, serverUrl: params.serverUrl };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, structure, propProps);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
|
||||
await AssemblySymmetryProvider.attach(propCtx, structure, { ...propProps, symmetryIndex });
|
||||
}));
|
||||
}
|
||||
|
||||
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
|
||||
const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
|
||||
const colorTheme = getRCSBAssemblySymmetryConfig(plugin).ApplyColors && assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
|
||||
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
|
||||
|
||||
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
|
||||
@@ -194,4 +198,27 @@ export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: Stat
|
||||
const assemblySymmetry = state.build().to(structure)
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
|
||||
return assemblySymmetry.commit({ revertOnError: true });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const RCSBAssemblySymmetryConfig = {
|
||||
DefaultServerType: new PluginConfigItem('rcsb-assembly-symmetry.server-type', AssemblySymmetryDataParams.serverType.defaultValue),
|
||||
DefaultServerUrl: new PluginConfigItem('rcsb-assembly-symmetry.server-url', AssemblySymmetryDataParams.serverUrl.defaultValue),
|
||||
ApplyColors: new PluginConfigItem('rcsb-assembly-symmetry.apply-colors', true),
|
||||
};
|
||||
|
||||
export function getRCSBAssemblySymmetryConfig(plugin: PluginContext): { [key in keyof typeof RCSBAssemblySymmetryConfig]: NonNullable<typeof RCSBAssemblySymmetryConfig[key]['defaultValue']> } {
|
||||
return {
|
||||
ApplyColors: plugin.config.get(RCSBAssemblySymmetryConfig.ApplyColors) ?? RCSBAssemblySymmetryConfig.ApplyColors.defaultValue ?? true,
|
||||
DefaultServerType: plugin.config.get(RCSBAssemblySymmetryConfig.DefaultServerType) ?? RCSBAssemblySymmetryConfig.DefaultServerType.defaultValue ?? AssemblySymmetryDataParams.serverType.defaultValue,
|
||||
DefaultServerUrl: plugin.config.get(RCSBAssemblySymmetryConfig.DefaultServerUrl) ?? RCSBAssemblySymmetryConfig.DefaultServerUrl.defaultValue ?? AssemblySymmetryDataParams.serverUrl.defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
function getConfiguredDefaultParams(plugin: PluginContext) {
|
||||
const config = getRCSBAssemblySymmetryConfig(plugin);
|
||||
const params = PD.clone(AssemblySymmetryDataParams);
|
||||
PD.setDefaultValues(params, { serverType: config.DefaultServerType, serverUrl: config.DefaultServerUrl });
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { SetUtils } from '../../../mol-util/set';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { compile } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
|
||||
const BiologicalAssemblyNames = new Set([
|
||||
'author_and_software_defined_assembly',
|
||||
@@ -48,7 +49,7 @@ export namespace AssemblySymmetry {
|
||||
Representation = 'rcsb-assembly-symmetry-3d'
|
||||
}
|
||||
|
||||
export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
|
||||
export const DefaultServerUrl = 'https://data.rcsb.org/graphql'; // Alternative: 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry' (if serverType is 'pdbe')
|
||||
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
return (
|
||||
@@ -61,6 +62,8 @@ export namespace AssemblySymmetry {
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
if (!isApplicable(structure)) return { value: [] };
|
||||
|
||||
if (props.serverType === 'pdbe') return fetch_PDBe(ctx, structure, props);
|
||||
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
|
||||
@@ -77,6 +80,37 @@ export namespace AssemblySymmetry {
|
||||
return { value, assets: [result] };
|
||||
}
|
||||
|
||||
async function fetch_PDBe(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
const assembly_id = structure.units[0].conformation.operator.assembly?.id || '-1'; // should use '' instead of '-1' but the API does not support non-number assembly_id
|
||||
const entry_id = structure.units[0].model.entryId.toLowerCase();
|
||||
const url = `${props.serverUrl}/${entry_id}?assembly_id=${assembly_id}`;
|
||||
const asset = Asset.getUrlAsset(ctx.assetManager, url);
|
||||
let dataWrapper: Asset.Wrapper<'json'>;
|
||||
try {
|
||||
dataWrapper = await ctx.assetManager.resolve(asset, 'json').runInContext(ctx.runtime);
|
||||
} catch (err) {
|
||||
// PDBe API returns 404 when there are no symmetries -> treat as success with empty json in body
|
||||
if (`${err}`.includes('404')) { // dirrrty
|
||||
dataWrapper = Asset.Wrapper({}, asset, ctx.assetManager);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const data = dataWrapper.data;
|
||||
|
||||
const value: AssemblySymmetryDataValue = (data[entry_id] ?? []).map((v: any) => ({
|
||||
kind: 'Global Symmetry',
|
||||
oligomeric_state: v.oligomeric_state,
|
||||
stoichiometry: [v.stoichiometry],
|
||||
symbol: v.symbol,
|
||||
type: v.type,
|
||||
clusters: [],
|
||||
rotation_axes: v.rotation_axes,
|
||||
}));
|
||||
|
||||
return { value, assets: [dataWrapper] };
|
||||
}
|
||||
|
||||
/** Returns the index of the first non C1 symmetry or -1 */
|
||||
export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
@@ -147,7 +181,8 @@ export function getSymmetrySelectParam(structure?: Structure) {
|
||||
//
|
||||
|
||||
export const AssemblySymmetryDataParams = {
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
|
||||
serverType: PD.Select('rcsb', [['rcsb', 'RCSB'], ['pdbe', 'PDBe']] as const),
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL (if server type is RCSB) or PDBe API endpoint URL (if server type is PDBe)' })
|
||||
};
|
||||
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
|
||||
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
|
||||
@@ -174,7 +209,7 @@ export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<Asse
|
||||
|
||||
function getAssemblySymmetryParams(data?: Structure) {
|
||||
return {
|
||||
... AssemblySymmetryDataParams,
|
||||
...AssemblySymmetryDataParams,
|
||||
symmetryIndex: getSymmetrySelectParam(data)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry, getRCSBAssemblySymmetryConfig } from './behavior';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
|
||||
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
@@ -72,6 +72,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
get params() {
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
|
||||
params.serverType.isHidden = true;
|
||||
params.serverUrl.isHidden = true;
|
||||
return params;
|
||||
}
|
||||
@@ -111,7 +112,9 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
}
|
||||
} else {
|
||||
tryCreateAssemblySymmetry(this.plugin, s.cell);
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
|
||||
if (getRCSBAssemblySymmetryConfig(this.plugin).ApplyColors) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,5 +154,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
const EnableAssemblySymmetry3D = StateAction.build({
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
|
||||
const presetParams = AssemblySymmetryPreset.params?.(a, plugin) as PD.Params | undefined;
|
||||
const presetProps = presetParams ? PD.getDefaultValues(presetParams) : Object.create(null);
|
||||
await AssemblySymmetryPreset.apply(ref, presetProps, plugin);
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -85,7 +85,7 @@ namespace ValidationReport {
|
||||
Clashes = 'rcsb-clashes',
|
||||
}
|
||||
|
||||
export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports';
|
||||
export const DefaultBaseUrl = 'https://files.rcsb.org/pub/pdb/validation_reports';
|
||||
export function getEntryUrl(pdbId: string, baseUrl: string) {
|
||||
const id = pdbId.toLowerCase();
|
||||
return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`;
|
||||
|
||||
47
src/extensions/wwpdb/ccd/README.md
Normal file
47
src/extensions/wwpdb/ccd/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Chemical Component Dictionary Extension
|
||||
|
||||
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) describes all small molecules and monomers found in PDB entries. The dictionary provides a plethora of additional information not present in wwPDB archive structures such as chemical descriptors (SMILES & InChI) and stereochemical assignments, information on bond order and more. Most notably, the CCD provides 2 sets of coordinates:
|
||||
- `ideal`: idealized/minimized coordinates, obtained using Molecular Networks' Corina, and if there are issues, OpenEye's OMEGA
|
||||
- `model`: coordinates extracted from an archive structure
|
||||
|
||||
## How to Load a Component from URL
|
||||
1. "Download Structure" -- switch "Source" to "URL"
|
||||
2. Enter URL of component, e.g. https://files.rcsb.org/ligands/view/HEM.cif, leave "Format" as is
|
||||
3. Click "Apply"
|
||||
|
||||
This parses the corresponding component into 2 models (1st: `ideal` coordinates, 2nd: `model` coordinates) and applies the default representaiton to the 1st model. `model` coordinates are available as 2nd model. Click the canvas to re-focus if you don't see anything after switching models due to the coordinates being far away.
|
||||
|
||||
## How to Visualize Components
|
||||
There's a dedicated representation preset that faciliates the comparison of `ideal` and `model` coordinates.
|
||||
|
||||
1. Load a component as described above
|
||||
2. Switch structure preset to "Chemical Component" (button in the top-right, in the "Structure" panel)
|
||||
|
||||
This creates a dedicated component for `ideal` as well as `model` coordinates and represents them as ball-and-stick. Initially, only `ideal` coordinates are shown. After toggling the visibility of `model` coordinates, they appear superimposed with the `ideal` coordinates.
|
||||
|
||||
## Examples & Test Cases
|
||||
Ligand | Description | Details
|
||||
-- | -- | --
|
||||
https://files.rcsb.org/ligands/view/HEM.cif | metal coordination |
|
||||
https://files.rcsb.org/ligands/view/FE.cif | +3 oxidation state |
|
||||
https://files.rcsb.org/ligands/view/FE2.cif | +2 oxidation state |
|
||||
https://files.rcsb.org/ligands/view/RUC.cif | transition metal |
|
||||
https://files.rcsb.org/ligands/view/SF4.cif | Fe-S cluster | doesn't align nicely
|
||||
https://files.rcsb.org/ligands/view/TBR.cif | coords identical |
|
||||
https://files.rcsb.org/ligands/view/OER.cif | coords identical |
|
||||
https://files.rcsb.org/ligands/view/FEA.cif | charges |
|
||||
https://files.rcsb.org/ligands/view/PR2.cif | orientation differs |
|
||||
https://files.rcsb.org/ligands/view/03R.cif | some atoms missing |
|
||||
https://files.rcsb.org/ligands/view/02U.cif | many atoms missing |
|
||||
https://files.rcsb.org/ligands/view/HC0.cif | no ideal coords | unrelated: O and H atoms clashing
|
||||
https://files.rcsb.org/ligands/view/Q6O.cif | no model coords |
|
||||
https://files.rcsb.org/ligands/view/H0C.cif | big ligand |
|
||||
https://files.rcsb.org/ligands/view/2NC.cif | dual representation as PRD and CC |
|
||||
https://files.rcsb.org/birds/view/PRDCC_000001.cif | PRDCC |
|
||||
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/BB87Q.cif | extended CCD identifier |
|
||||
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/7ZTVU.cif | extended CCD identifier |
|
||||
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/9QRZS.cif | extended CCD identifier |
|
||||
https://raw.githubusercontent.com/wwPDB/extended-wwPDB-identifier-examples/main/CCD/9ABCD.cif | extended CCD identifier |
|
||||
https://files.rcsb.org/ligands/view/UNK.cif | CCD special: unknown amino acid | unrelated: some model H are placed far away
|
||||
https://files.rcsb.org/ligands/view/UNX.cif | CCD special: unknown atom/ion | no ideal coordinates
|
||||
https://files.rcsb.org/ligands/view/UNL.cif | CCD special: unknown ligand | no coordinates whatsoever
|
||||
33
src/extensions/wwpdb/ccd/behavior.ts
Normal file
33
src/extensions/wwpdb/ccd/behavior.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { ChemicalComponentPreset, ChemicalCompontentTrajectoryHierarchyPreset } from './representation';
|
||||
|
||||
export const wwPDBChemicalComponentDictionary = PluginBehavior.create<{ }>({
|
||||
name: 'wwpdb-chemical-component-dictionary',
|
||||
category: 'representation',
|
||||
display: {
|
||||
name: 'wwPDB Chemical Compontent Dictionary',
|
||||
description: 'Custom representation for data loaded from the CCD.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ }> {
|
||||
register(): void {
|
||||
this.ctx.builders.structure.hierarchy.registerPreset(ChemicalCompontentTrajectoryHierarchyPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(ChemicalComponentPreset);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.builders.structure.hierarchy.unregisterPreset(ChemicalCompontentTrajectoryHierarchyPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ChemicalComponentPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({ })
|
||||
});
|
||||
167
src/extensions/wwpdb/ccd/representation.ts
Normal file
167
src/extensions/wwpdb/ccd/representation.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StateObjectRef, StateTransform } from '../../../mol-state';
|
||||
import { StateTransforms } from '../../../mol-plugin-state/transforms';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { CCDFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { MinimizeRmsd } from '../../../mol-math/linear-algebra/3d/minimize-rmsd';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { TrajectoryHierarchyPresetProvider } from '../../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { capitalize } from '../../../mol-util/string';
|
||||
|
||||
const CCDParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
representationPresetParams: PD.Optional(PD.Group(StructureRepresentationPresetProvider.CommonParams)),
|
||||
showOriginalCoordinates: PD.Optional(PD.Boolean(true, { description: `Show original coordinates for 'model' and 'ideal' structure and do not align them.` })),
|
||||
shownCoordinateType: PD.Select('ideal', PD.arrayToOptions(['ideal', 'model', 'both'] as const), { description: `What coordinate sets are visible.` }),
|
||||
aromaticBonds: PD.Boolean(false, { description: 'Display aromatic bonds with dashes' }),
|
||||
...TrajectoryHierarchyPresetProvider.CommonParams(a, plugin)
|
||||
});
|
||||
|
||||
export const ChemicalCompontentTrajectoryHierarchyPreset = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-ccd',
|
||||
display: {
|
||||
name: 'Chemical Component', group: 'Preset',
|
||||
description: 'Shows molecules from the Chemical Component Dictionary.'
|
||||
},
|
||||
isApplicable: o => {
|
||||
return CCDFormat.is(o.data.representative.sourceData);
|
||||
},
|
||||
params: CCDParams,
|
||||
async apply(trajectory, params, plugin) {
|
||||
const tr = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory)?.obj?.data;
|
||||
if (!tr) return {};
|
||||
|
||||
const builder = plugin.builders.structure;
|
||||
|
||||
const idealModel = await builder.createModel(trajectory, { modelIndex: 0 });
|
||||
const idealModelProperties = await builder.insertModelProperties(idealModel, params.modelProperties, { isCollapsed: true });
|
||||
|
||||
const idealStructure = await builder.createStructure(idealModelProperties || idealModel, { name: 'model', params: {} });
|
||||
const idealStructureProperties = await builder.insertStructureProperties(idealStructure, params.structureProperties);
|
||||
|
||||
const representationPreset = params.representationPreset || ChemicalComponentPreset.id;
|
||||
const representationPresetParams = params.representationPresetParams || {};
|
||||
if (representationPresetParams.ignoreHydrogens === undefined) representationPresetParams.ignoreHydrogens = true;
|
||||
|
||||
// degenerate case where either model or ideal coordinates are missing
|
||||
if (tr.frameCount !== 2) {
|
||||
const coordinateType = CCDFormat.CoordinateType.get(idealModel.obj!.data);
|
||||
await builder.representation.applyPreset(idealStructureProperties, representationPreset, { ...representationPresetParams, coordinateType });
|
||||
|
||||
return { models: [idealModel], structures: [idealStructure] };
|
||||
}
|
||||
|
||||
const modelModel = await builder.createModel(trajectory, { modelIndex: 1 });
|
||||
const modelModelProperties = await builder.insertModelProperties(modelModel, params.modelProperties, { isCollapsed: true });
|
||||
|
||||
const modelStructure = await builder.createStructure(modelModelProperties || modelModel, { name: 'model', params: {} });
|
||||
const modelStructureProperties = await builder.insertStructureProperties(modelStructure, params.structureProperties);
|
||||
|
||||
// align ideal and model coordinates
|
||||
if (!params.showOriginalCoordinates) {
|
||||
const [a, b] = getPositionTables(idealStructure.obj!.data, modelStructure.obj!.data);
|
||||
if (!a) {
|
||||
plugin.log.warn(`Cannot align chemical components whose atom sets are disjoint.`);
|
||||
} else {
|
||||
const { bTransform, rmsd } = MinimizeRmsd.compute({ a, b });
|
||||
await transform(plugin, modelStructure.cell!, bTransform);
|
||||
plugin.log.info(`Superposed [model] and [ideal] with RMSD ${rmsd.toFixed(2)}.`);
|
||||
}
|
||||
}
|
||||
|
||||
await builder.representation.applyPreset(idealStructureProperties, representationPreset, { ...representationPresetParams, aromaticBonds: params.aromaticBonds, coordinateType: 'ideal', isHidden: params.shownCoordinateType === 'model' });
|
||||
await builder.representation.applyPreset(modelStructureProperties, representationPreset, { ...representationPresetParams, aromaticBonds: params.aromaticBonds, coordinateType: 'model', isHidden: params.shownCoordinateType === 'ideal' });
|
||||
|
||||
return { models: [idealModel, modelModel], structures: [idealStructure, modelStructure] };
|
||||
}
|
||||
});
|
||||
|
||||
function getPositionTables(s1: Structure, s2: Structure) {
|
||||
const m1 = getAtomIdSerialMap(s1);
|
||||
const m2 = getAtomIdSerialMap(s2);
|
||||
const intersecting = SetUtils.intersection(new Set(m1.keys()), new Set(m2.keys()));
|
||||
|
||||
const ret = [
|
||||
MinimizeRmsd.Positions.empty(intersecting.size),
|
||||
MinimizeRmsd.Positions.empty(intersecting.size)
|
||||
];
|
||||
let o = 0;
|
||||
intersecting.forEach(k => {
|
||||
ret[0].x[o] = s1.model.atomicConformation.x[m1.get(k)!];
|
||||
ret[0].y[o] = s1.model.atomicConformation.y[m1.get(k)!];
|
||||
ret[0].z[o] = s1.model.atomicConformation.z[m1.get(k)!];
|
||||
ret[1].x[o] = s2.model.atomicConformation.x[m2.get(k)!];
|
||||
ret[1].y[o] = s2.model.atomicConformation.y[m2.get(k)!];
|
||||
ret[1].z[o] = s2.model.atomicConformation.z[m2.get(k)!];
|
||||
o++;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getAtomIdSerialMap(structure: Structure) {
|
||||
const map = new Map<string, number>();
|
||||
const { label_atom_id } = structure.model.atomicHierarchy.atoms;
|
||||
for (let i = 0, il = label_atom_id.rowCount; i < il; ++i) {
|
||||
const id = label_atom_id.value(i);
|
||||
if (!map.has(id)) map.set(id, map.size);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function transform(plugin: PluginContext, s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4) {
|
||||
const b = plugin.state.data.build().to(s)
|
||||
.insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
|
||||
return plugin.runTask(plugin.state.data.updateTree(b));
|
||||
}
|
||||
|
||||
export const ChemicalComponentPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-chemical-component',
|
||||
display: {
|
||||
name: 'Chemical Component', group: 'Miscellaneous',
|
||||
description: `Show 'Ideal' and 'Model' coordinates of chemical components.`
|
||||
},
|
||||
isApplicable: o => {
|
||||
return CCDFormat.is(o.data.model.sourceData);
|
||||
},
|
||||
params: () => ({
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
aromaticBonds: PD.Boolean(true),
|
||||
coordinateType: PD.Select<CCDFormat.CoordinateType>('ideal', PD.arrayToOptions(['ideal', 'model'])),
|
||||
isHidden: PD.Boolean(false),
|
||||
}),
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const { aromaticBonds, coordinateType, isHidden } = params;
|
||||
const components = {
|
||||
[coordinateType]: await presetStaticComponent(plugin, structureCell, 'all', { label: capitalize(coordinateType), tags: [coordinateType] })
|
||||
};
|
||||
|
||||
const structure = structureCell.obj!.data;
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
|
||||
const representations = {
|
||||
[coordinateType]: builder.buildRepresentation(update, components[coordinateType], { type: 'ball-and-stick', typeParams: { ...typeParams, aromaticBonds } }, { initialState: { isHidden } }),
|
||||
};
|
||||
// sync UI state
|
||||
if (components[coordinateType]?.cell?.state && isHidden) {
|
||||
StateTransform.assignState(components[coordinateType]!.cell!.state, { isHidden });
|
||||
}
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
await StructureRepresentationPresetProvider.updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
|
||||
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
@@ -107,13 +107,13 @@ export { Canvas3DContext };
|
||||
|
||||
/** Can be used to create multiple Canvas3D objects */
|
||||
interface Canvas3DContext {
|
||||
readonly canvas: HTMLCanvasElement
|
||||
readonly canvas?: HTMLCanvasElement
|
||||
readonly webgl: WebGLContext
|
||||
readonly input: InputObserver
|
||||
readonly passes: Passes
|
||||
readonly attribs: Readonly<Canvas3DContext.Attribs>
|
||||
readonly contextLost: BehaviorSubject<now.Timestamp>
|
||||
readonly contextRestored: BehaviorSubject<now.Timestamp>
|
||||
readonly contextLost?: BehaviorSubject<now.Timestamp>
|
||||
readonly contextRestored?: BehaviorSubject<now.Timestamp>
|
||||
readonly assetManager: AssetManager
|
||||
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
|
||||
}
|
||||
@@ -445,7 +445,7 @@ namespace Canvas3D {
|
||||
if (isTimingMode) webgl.timer.mark('Canvas3D.render', true);
|
||||
const ctx = { renderer, camera: cam, scene, helper };
|
||||
if (MultiSamplePass.isEnabled(p.multiSample)) {
|
||||
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
|
||||
const forceOn = p.multiSample.reduceFlicker && !cameraChanged && markingUpdated && !controls.isAnimating;
|
||||
multiSampleHelper.render(ctx, p, true, forceOn);
|
||||
} else {
|
||||
passes.draw.render(ctx, p, true);
|
||||
|
||||
@@ -638,45 +638,45 @@ namespace TrackballControls {
|
||||
Vec2.copy(_rotCurr, getMouseOnCircle(movementX + cx, movementY + cy));
|
||||
}
|
||||
|
||||
function onKeyDown({ modifiers, code, x, y }: KeyInput) {
|
||||
function onKeyDown({ modifiers, code, key, x, y }: KeyInput) {
|
||||
if (outsideViewport(x, y)) return;
|
||||
|
||||
if (Binding.matchKey(b.keyMoveForward, code, modifiers)) {
|
||||
if (Binding.matchKey(b.keyMoveForward, code, modifiers, key)) {
|
||||
keyState.moveForward = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers, key)) {
|
||||
keyState.moveBack = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers, key)) {
|
||||
keyState.moveLeft = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers, key)) {
|
||||
keyState.moveRight = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers, key)) {
|
||||
keyState.moveUp = 1;
|
||||
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers, key)) {
|
||||
keyState.moveDown = 1;
|
||||
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers, key)) {
|
||||
keyState.rollLeft = 1;
|
||||
} else if (Binding.matchKey(b.keyRollRight, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyRollRight, code, modifiers, key)) {
|
||||
keyState.rollRight = 1;
|
||||
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers, key)) {
|
||||
keyState.pitchUp = 1;
|
||||
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers, key)) {
|
||||
keyState.pitchDown = 1;
|
||||
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers, key)) {
|
||||
keyState.yawLeft = 1;
|
||||
} else if (Binding.matchKey(b.keyYawRight, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyYawRight, code, modifiers, key)) {
|
||||
keyState.yawRight = 1;
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.boostMove, code, modifiers)) {
|
||||
if (Binding.matchKey(b.boostMove, code, modifiers, key)) {
|
||||
keyState.boostMove = 1;
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.enablePointerLock, code, modifiers)) {
|
||||
if (Binding.matchKey(b.enablePointerLock, code, modifiers, key)) {
|
||||
input.requestPointerLock(viewport);
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyUp({ modifiers, code, x, y }: KeyInput) {
|
||||
function onKeyUp({ modifiers, code, key, x, y }: KeyInput) {
|
||||
if (outsideViewport(x, y)) return;
|
||||
|
||||
let isModifierCode = false;
|
||||
@@ -715,34 +715,34 @@ namespace TrackballControls {
|
||||
}
|
||||
|
||||
for (const code of codes) {
|
||||
if (Binding.matchKey(b.keyMoveForward, code, modifiers)) {
|
||||
if (Binding.matchKey(b.keyMoveForward, code, modifiers, key)) {
|
||||
keyState.moveForward = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveBack, code, modifiers, key)) {
|
||||
keyState.moveBack = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveLeft, code, modifiers, key)) {
|
||||
keyState.moveLeft = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveRight, code, modifiers, key)) {
|
||||
keyState.moveRight = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveUp, code, modifiers, key)) {
|
||||
keyState.moveUp = 0;
|
||||
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyMoveDown, code, modifiers, key)) {
|
||||
keyState.moveDown = 0;
|
||||
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyRollLeft, code, modifiers, key)) {
|
||||
keyState.rollLeft = 0;
|
||||
} else if (Binding.matchKey(b.keyRollRight, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyRollRight, code, modifiers, key)) {
|
||||
keyState.rollRight = 0;
|
||||
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyPitchUp, code, modifiers, key)) {
|
||||
keyState.pitchUp = 0;
|
||||
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyPitchDown, code, modifiers, key)) {
|
||||
keyState.pitchDown = 0;
|
||||
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyYawLeft, code, modifiers, key)) {
|
||||
keyState.yawLeft = 0;
|
||||
} else if (Binding.matchKey(b.keyYawRight, code, modifiers)) {
|
||||
} else if (Binding.matchKey(b.keyYawRight, code, modifiers, key)) {
|
||||
keyState.yawRight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.boostMove, code, modifiers)) {
|
||||
if (Binding.matchKey(b.boostMove, code, modifiers, key)) {
|
||||
keyState.boostMove = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -23,6 +23,7 @@ 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';
|
||||
import { isPowerOfTwo } from '../../mol-math/misc';
|
||||
|
||||
const SharedParams = {
|
||||
opacity: PD.Numeric(1, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
@@ -59,6 +60,7 @@ const ImageParams = {
|
||||
url: PD.Text(''),
|
||||
file: PD.File({ accept: 'image/*' }),
|
||||
}),
|
||||
blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or with power-of-two images and when "EXT_shader_texture_lod" is available.' }),
|
||||
...SharedParams,
|
||||
coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])),
|
||||
};
|
||||
@@ -207,6 +209,7 @@ export class BackgroundPass {
|
||||
}
|
||||
if (!this.image) return;
|
||||
|
||||
ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness);
|
||||
@@ -369,6 +372,12 @@ function getSkyboxTexture(ctx: WebGLContext, assetManager: AssetManager, faces:
|
||||
const cubeAssets = getCubeAssets(assetManager, faces);
|
||||
const cubeFaces = getCubeFaces(assetManager, cubeAssets);
|
||||
const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz];
|
||||
if (typeof HTMLImageElement === 'undefined') {
|
||||
console.error(`Missing "HTMLImageElement" required for background skybox`);
|
||||
onload?.(true);
|
||||
return { texture: createNullTexture(), assets };
|
||||
}
|
||||
|
||||
const texture = ctx.resources.cubeTexture(cubeFaces, true, onload);
|
||||
return { texture, assets };
|
||||
}
|
||||
@@ -390,18 +399,28 @@ function areImageTexturePropsEqual(sourceA: ImageProps['source'], sourceB: Image
|
||||
}
|
||||
|
||||
function getImageTexture(ctx: WebGLContext, assetManager: AssetManager, source: ImageProps['source'], onload?: (errored?: boolean) => void): { texture: Texture, asset: Asset } {
|
||||
const asset = source.name === 'url'
|
||||
? Asset.getUrlAsset(assetManager, source.params)
|
||||
: source.params!;
|
||||
if (typeof HTMLImageElement === 'undefined') {
|
||||
console.error(`Missing "HTMLImageElement" required for background image`);
|
||||
onload?.(true);
|
||||
return { texture: createNullTexture(), asset };
|
||||
}
|
||||
|
||||
const texture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
texture.load(img);
|
||||
if (ctx.isWebGL2 || (isPowerOfTwo(img.width) && isPowerOfTwo(img.height))) {
|
||||
texture.mipmap();
|
||||
}
|
||||
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);
|
||||
|
||||
120
src/mol-canvas3d/passes/cas.ts
Normal file
120
src/mol-canvas3d/passes/cas.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { quad_vert } from '../../mol-gl/shader/quad.vert';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { isTimingMode } from '../../mol-util/debug';
|
||||
import { cas_frag } from '../../mol-gl/shader/cas.frag';
|
||||
|
||||
export const CasParams = {
|
||||
sharpness: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }),
|
||||
denoise: PD.Boolean(true),
|
||||
};
|
||||
export type CasProps = PD.Values<typeof CasParams>
|
||||
|
||||
export class CasPass {
|
||||
private readonly renderable: CasRenderable;
|
||||
|
||||
constructor(private webgl: WebGLContext, input: Texture) {
|
||||
this.renderable = getCasRenderable(webgl, input);
|
||||
}
|
||||
|
||||
private updateState(viewport: Viewport) {
|
||||
const { gl, state } = this.webgl;
|
||||
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
const { x, y, width, height } = viewport;
|
||||
state.viewport(x, y, width, height);
|
||||
state.scissor(x, y, width, height);
|
||||
|
||||
state.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
ValueCell.update(this.renderable.values.uTexSizeInv, Vec2.set(this.renderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
|
||||
}
|
||||
|
||||
update(input: Texture, props: CasProps) {
|
||||
const { values } = this.renderable;
|
||||
const { sharpness, denoise } = props;
|
||||
|
||||
let needsUpdate = false;
|
||||
|
||||
if (values.tColor.ref.value !== input) {
|
||||
ValueCell.update(this.renderable.values.tColor, input);
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
ValueCell.updateIfChanged(values.uSharpness, 2 - 2 * Math.pow(sharpness, 0.25));
|
||||
|
||||
if (values.dDenoise.ref.value !== denoise) needsUpdate = true;
|
||||
ValueCell.updateIfChanged(values.dDenoise, denoise);
|
||||
|
||||
if (needsUpdate) {
|
||||
this.renderable.update();
|
||||
}
|
||||
}
|
||||
|
||||
render(viewport: Viewport, target: RenderTarget | undefined) {
|
||||
if (isTimingMode) this.webgl.timer.mark('CasPass.render');
|
||||
if (target) {
|
||||
target.bind();
|
||||
} else {
|
||||
this.webgl.unbindFramebuffer();
|
||||
}
|
||||
this.updateState(viewport);
|
||||
this.renderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('CasPass.render');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const CasSchema = {
|
||||
...QuadSchema,
|
||||
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
|
||||
uTexSizeInv: UniformSpec('v2'),
|
||||
|
||||
uSharpness: UniformSpec('f'),
|
||||
dDenoise: DefineSpec('boolean'),
|
||||
};
|
||||
const CasShaderCode = ShaderCode('cas', quad_vert, cas_frag);
|
||||
type CasRenderable = ComputeRenderable<Values<typeof CasSchema>>
|
||||
|
||||
function getCasRenderable(ctx: WebGLContext, colorTexture: Texture): CasRenderable {
|
||||
const width = colorTexture.getWidth();
|
||||
const height = colorTexture.getHeight();
|
||||
|
||||
const values: Values<typeof CasSchema> = {
|
||||
...QuadValues,
|
||||
tColor: ValueCell.create(colorTexture),
|
||||
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
|
||||
|
||||
uSharpness: ValueCell.create(0.5),
|
||||
dDenoise: ValueCell.create(true),
|
||||
};
|
||||
|
||||
const schema = { ...CasSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', CasShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
@@ -115,19 +115,19 @@ export class DrawPass {
|
||||
|
||||
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
|
||||
|
||||
if (this.wboit?.supported) {
|
||||
this.wboit.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.dpoit?.supported) {
|
||||
this.dpoit.setSize(width, height);
|
||||
}
|
||||
|
||||
this.marking.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.antialiasing.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.wboit?.supported) {
|
||||
this.wboit.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.dpoit?.supported) {
|
||||
this.dpoit.setSize(width, height);
|
||||
}
|
||||
|
||||
this.marking.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.antialiasing.setSize(width, height);
|
||||
}
|
||||
|
||||
private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
|
||||
@@ -53,6 +53,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
|
||||
export const MultiSampleParams = {
|
||||
mode: PD.Select('temporal', [['off', 'Off'], ['on', 'On'], ['temporal', 'Temporal']]),
|
||||
sampleLevel: PD.Numeric(2, { min: 0, max: 5, step: 1 }, { description: 'Take level^2 samples.' }),
|
||||
reduceFlicker: PD.Boolean(true, { description: 'Reduce flicker in "temporal" mode.' }),
|
||||
};
|
||||
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import { BackgroundParams, BackgroundPass } from './background';
|
||||
import { AssetManager } from '../../mol-util/assets';
|
||||
import { Light } from '../../mol-gl/renderer';
|
||||
import { shadows_frag } from '../../mol-gl/shader/shadows.frag';
|
||||
import { CasParams, CasPass } from './cas';
|
||||
|
||||
const OutlinesSchema = {
|
||||
...QuadSchema,
|
||||
@@ -399,6 +400,10 @@ export const PostprocessingParams = {
|
||||
smaa: PD.Group(SmaaParams),
|
||||
off: PD.Group({})
|
||||
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
|
||||
sharpening: PD.MappedStatic('off', {
|
||||
on: PD.Group(CasParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Contrast Adaptive Sharpening' }),
|
||||
background: PD.Group(BackgroundParams, { isFlat: true }),
|
||||
};
|
||||
|
||||
@@ -464,14 +469,13 @@ export class PostprocessingPass {
|
||||
|
||||
private nSamples: number;
|
||||
private blurKernelSize: number;
|
||||
private downsampleFactor: number;
|
||||
|
||||
private readonly renderable: PostprocessingRenderable;
|
||||
|
||||
private ssaoScale: number;
|
||||
private calcSsaoScale() {
|
||||
private calcSsaoScale(resolutionScale: number) {
|
||||
// downscale ssao for high pixel-ratios
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio) * resolutionScale;
|
||||
}
|
||||
|
||||
private levels: { radius: number, bias: number }[];
|
||||
@@ -486,8 +490,7 @@ export class PostprocessingPass {
|
||||
|
||||
this.nSamples = 1;
|
||||
this.blurKernelSize = 1;
|
||||
this.downsampleFactor = 1;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
this.ssaoScale = this.calcSsaoScale(1);
|
||||
this.levels = [];
|
||||
|
||||
// needs to be linear for anti-aliasing pass
|
||||
@@ -517,10 +520,12 @@ export class PostprocessingPass {
|
||||
: webgl.createRenderTarget(sw, sh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
|
||||
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
|
||||
|
||||
const depthTexture = this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture;
|
||||
|
||||
this.depthHalfTarget = drawPass.packedDepth
|
||||
? webgl.createRenderTarget(hw, hh, false, 'uint8', 'linear', 'rgba')
|
||||
: webgl.createRenderTarget(hw, hh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
|
||||
this.depthHalfRenderable = createCopyRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
|
||||
this.depthHalfRenderable = createCopyRenderable(webgl, depthTexture);
|
||||
|
||||
this.depthQuarterTarget = drawPass.packedDepth
|
||||
? webgl.createRenderTarget(qw, qh, false, 'uint8', 'linear', 'rgba')
|
||||
@@ -537,7 +542,7 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture, this.depthHalfTarget.texture, this.depthQuarterTarget.texture);
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture, this.depthHalfTarget.texture, this.depthQuarterTarget.texture);
|
||||
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
|
||||
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
|
||||
@@ -547,7 +552,7 @@ export class PostprocessingPass {
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
const ssaoScale = this.calcSsaoScale();
|
||||
const ssaoScale = this.calcSsaoScale(1);
|
||||
|
||||
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
|
||||
this.ssaoScale = ssaoScale;
|
||||
@@ -580,6 +585,13 @@ export class PostprocessingPass {
|
||||
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));
|
||||
|
||||
const depthTexture = this.ssaoScale === 1 ? this.drawPass.depthTextureOpaque : this.downsampledDepthTarget.texture;
|
||||
ValueCell.update(this.depthHalfRenderable.values.tColor, depthTexture);
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, depthTexture);
|
||||
|
||||
this.depthHalfRenderable.update();
|
||||
this.ssaoRenderable.update();
|
||||
|
||||
this.background.setSize(width, height);
|
||||
}
|
||||
}
|
||||
@@ -589,6 +601,7 @@ export class PostprocessingPass {
|
||||
let needsUpdateMain = false;
|
||||
let needsUpdateSsao = false;
|
||||
let needsUpdateSsaoBlur = false;
|
||||
let needsUpdateDepthHalf = false;
|
||||
let needsUpdateOutlines = false;
|
||||
|
||||
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
|
||||
@@ -678,11 +691,12 @@ export class PostprocessingPass {
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
}
|
||||
|
||||
if (this.downsampleFactor !== props.occlusion.params.resolutionScale) {
|
||||
const ssaoScale = this.calcSsaoScale(props.occlusion.params.resolutionScale);
|
||||
if (this.ssaoScale !== ssaoScale) {
|
||||
needsUpdateSsao = true;
|
||||
needsUpdateDepthHalf = true;
|
||||
|
||||
this.downsampleFactor = props.occlusion.params.resolutionScale;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
this.ssaoScale = ssaoScale;
|
||||
|
||||
const sw = Math.floor(w * this.ssaoScale);
|
||||
const sh = Math.floor(h * this.ssaoScale);
|
||||
@@ -698,11 +712,9 @@ export class PostprocessingPass {
|
||||
const qh = Math.floor(sh * 0.25);
|
||||
this.depthQuarterTarget.setSize(qw, qh);
|
||||
|
||||
if (this.ssaoScale === 1) {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureOpaque);
|
||||
} else {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
|
||||
}
|
||||
const depthTexture = this.ssaoScale === 1 ? this.drawPass.depthTextureOpaque : this.downsampledDepthTarget.texture;
|
||||
ValueCell.update(this.depthHalfRenderable.values.tColor, depthTexture);
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, depthTexture);
|
||||
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepthHalf, this.depthHalfTarget.texture);
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepthQuarter, this.depthQuarterTarget.texture);
|
||||
@@ -755,8 +767,8 @@ export class PostprocessingPass {
|
||||
|
||||
if (props.outline.name === 'on') {
|
||||
const transparentOutline = props.outline.params.includeTransparent ?? true;
|
||||
const outlineScale = props.outline.params.scale - 1;
|
||||
const outlineThreshold = 50 * props.outline.params.threshold;
|
||||
const outlineScale = Math.max(1, Math.round(props.outline.params.scale * this.webgl.pixelRatio)) - 1;
|
||||
const outlineThreshold = 50 * props.outline.params.threshold * this.webgl.pixelRatio;
|
||||
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
|
||||
@@ -824,6 +836,10 @@ export class PostprocessingPass {
|
||||
this.ssaoBlurSecondPassRenderable.update();
|
||||
}
|
||||
|
||||
if (needsUpdateDepthHalf) {
|
||||
this.depthHalfRenderable.update();
|
||||
}
|
||||
|
||||
if (needsUpdateMain) {
|
||||
this.renderable.update();
|
||||
}
|
||||
@@ -942,8 +958,11 @@ export class AntialiasingPass {
|
||||
}
|
||||
|
||||
readonly target: RenderTarget;
|
||||
private readonly internalTarget: RenderTarget;
|
||||
|
||||
private readonly fxaa: FxaaPass;
|
||||
private readonly smaa: SmaaPass;
|
||||
private readonly cas: CasPass;
|
||||
|
||||
constructor(webgl: WebGLContext, private drawPass: DrawPass) {
|
||||
const { colorTarget } = drawPass;
|
||||
@@ -951,8 +970,11 @@ export class AntialiasingPass {
|
||||
const height = colorTarget.getHeight();
|
||||
|
||||
this.target = webgl.createRenderTarget(width, height, false);
|
||||
this.internalTarget = webgl.createRenderTarget(width, height, false);
|
||||
|
||||
this.fxaa = new FxaaPass(webgl, this.target.texture);
|
||||
this.smaa = new SmaaPass(webgl, this.target.texture);
|
||||
this.cas = new CasPass(webgl, this.target.texture);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -961,41 +983,69 @@ export class AntialiasingPass {
|
||||
|
||||
if (width !== w || height !== h) {
|
||||
this.target.setSize(width, height);
|
||||
this.internalTarget.setSize(width, height);
|
||||
this.fxaa.setSize(width, height);
|
||||
if (this.smaa.supported) this.smaa.setSize(width, height);
|
||||
this.cas.setSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
private _renderFxaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
|
||||
if (props.antialiasing.name !== 'fxaa') return;
|
||||
|
||||
const input = PostprocessingPass.isEnabled(props)
|
||||
? this.drawPass.postprocessing.target.texture
|
||||
: this.drawPass.colorTarget.texture;
|
||||
this.fxaa.update(input, props.antialiasing.params);
|
||||
this.fxaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
|
||||
this.fxaa.render(camera.viewport, target);
|
||||
}
|
||||
|
||||
private _renderSmaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
private _renderSmaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
|
||||
if (props.antialiasing.name !== 'smaa') return;
|
||||
|
||||
const input = PostprocessingPass.isEnabled(props)
|
||||
? this.drawPass.postprocessing.target.texture
|
||||
: this.drawPass.colorTarget.texture;
|
||||
this.smaa.update(input, props.antialiasing.params);
|
||||
this.smaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
|
||||
this.smaa.render(camera.viewport, target);
|
||||
}
|
||||
|
||||
private _renderAntialiasing(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
|
||||
if (props.antialiasing.name === 'fxaa') {
|
||||
this._renderFxaa(camera, target, props);
|
||||
} else if (props.antialiasing.name === 'smaa') {
|
||||
this._renderSmaa(camera, target, props);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderCas(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
|
||||
if (props.sharpening.name !== 'on') return;
|
||||
|
||||
const input = props.antialiasing.name !== 'off'
|
||||
? this.internalTarget.texture
|
||||
: PostprocessingPass.isEnabled(props)
|
||||
? this.drawPass.postprocessing.target.texture
|
||||
: this.drawPass.colorTarget.texture;
|
||||
this.cas.update(input, props.sharpening.params);
|
||||
this.cas.render(camera.viewport, target);
|
||||
}
|
||||
|
||||
render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
if (props.antialiasing.name === 'off') return;
|
||||
if (props.antialiasing.name === 'off' && props.sharpening.name === 'off') return;
|
||||
|
||||
if (props.antialiasing.name === 'fxaa') {
|
||||
this._renderFxaa(camera, toDrawingBuffer, props);
|
||||
} else if (props.antialiasing.name === 'smaa') {
|
||||
if (!this.smaa.supported) {
|
||||
throw new Error('SMAA not supported, missing "HTMLImageElement"');
|
||||
}
|
||||
this._renderSmaa(camera, toDrawingBuffer, props);
|
||||
if (props.antialiasing.name === 'smaa' && !this.smaa.supported) {
|
||||
console.error('SMAA not supported, missing "HTMLImageElement"');
|
||||
return;
|
||||
}
|
||||
|
||||
const target = toDrawingBuffer ? undefined : this.target;
|
||||
if (props.sharpening.name === 'off') {
|
||||
this._renderAntialiasing(camera, target, props);
|
||||
} else if (props.antialiasing.name === 'off') {
|
||||
this._renderCas(camera, target, props);
|
||||
} else {
|
||||
this._renderAntialiasing(camera, this.internalTarget, props);
|
||||
this._renderCas(camera, target, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export namespace BaseGeometry {
|
||||
if (!transform) transform = createIdentityTransform();
|
||||
const locationIterator = LocationIterator(1, transform.instanceCount.ref.value, 1, () => NullLocation, false, () => false);
|
||||
const theme: Theme = {
|
||||
color: UniformColorTheme({}, { value: colorValue }),
|
||||
color: UniformColorTheme({}, { value: colorValue, lightness: 0, saturation: 0 }),
|
||||
size: UniformSizeTheme({}, { value: sizeValue })
|
||||
};
|
||||
return { transform, locationIterator, theme };
|
||||
|
||||
@@ -156,7 +156,7 @@ export namespace Cylinders {
|
||||
sizeAspectRatio: PD.Numeric(1, { min: 0, max: 3, step: 0.01 }),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
|
||||
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
@@ -244,7 +244,7 @@ export namespace Cylinders {
|
||||
uSizeFactor: ValueCell.create(props.sizeFactor * props.sizeAspectRatio),
|
||||
uDoubleSided: ValueCell.create(props.doubleSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
dSolidInterior: ValueCell.create(props.solidInterior),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
@@ -263,7 +263,7 @@ export namespace Cylinders {
|
||||
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor * props.sizeAspectRatio);
|
||||
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
|
||||
@@ -147,7 +147,7 @@ export namespace DirectVolume {
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
controlPoints: PD.LineGraph([
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
|
||||
@@ -269,7 +269,7 @@ export namespace DirectVolume {
|
||||
dAxisOrder: ValueCell.create(directVolume.axisOrder.ref.value.join('')),
|
||||
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ export namespace DirectVolume {
|
||||
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
|
||||
BaseGeometry.updateValues(values, props);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
|
||||
createTransferFunctionTexture(controlPoints, values.tTransferTex);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -81,7 +81,7 @@ export namespace Geometry {
|
||||
switch (geometry.kind) {
|
||||
case 'mesh': return geometry.vertexCount;
|
||||
case 'points': return geometry.pointCount;
|
||||
case 'spheres': return geometry.sphereCount * 4;
|
||||
case 'spheres': return geometry.sphereCount * 6;
|
||||
case 'cylinders': return geometry.cylinderCount * 6;
|
||||
case 'text': return geometry.charCount * 4;
|
||||
case 'lines': return geometry.lineCount * 4;
|
||||
|
||||
@@ -627,7 +627,7 @@ export namespace Mesh {
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
@@ -706,7 +706,7 @@ export namespace Mesh {
|
||||
dFlatShaded: ValueCell.create(props.flatShaded),
|
||||
dFlipSided: ValueCell.create(props.flipSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
@@ -727,7 +727,7 @@ export namespace Mesh {
|
||||
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
|
||||
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -7,21 +7,8 @@
|
||||
import { ChunkedArray } from '../../../mol-data/util';
|
||||
import { Spheres } from './spheres';
|
||||
|
||||
const quadMapping = new Float32Array([
|
||||
-1.0, 1.0,
|
||||
-1.0, -1.0,
|
||||
1.0, 1.0,
|
||||
1.0, -1.0
|
||||
]);
|
||||
|
||||
const quadIndices = new Uint16Array([
|
||||
0, 1, 2,
|
||||
1, 3, 2
|
||||
]);
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const caAdd3 = ChunkedArray.add3;
|
||||
const caAdd2 = ChunkedArray.add2;
|
||||
const caAdd = ChunkedArray.add;
|
||||
|
||||
export interface SpheresBuilder {
|
||||
@@ -31,30 +18,18 @@ export interface SpheresBuilder {
|
||||
|
||||
export namespace SpheresBuilder {
|
||||
export function create(initialCount = 2048, chunkSize = 1024, spheres?: Spheres): SpheresBuilder {
|
||||
initialCount *= 4;
|
||||
chunkSize *= 4;
|
||||
const centers = ChunkedArray.create(Float32Array, 3, chunkSize, spheres ? spheres.centerBuffer.ref.value : initialCount);
|
||||
const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, spheres ? spheres.mappingBuffer.ref.value : initialCount);
|
||||
const indices = ChunkedArray.create(Uint32Array, 3, chunkSize / 2, spheres ? spheres.indexBuffer.ref.value : initialCount / 2);
|
||||
const groups = ChunkedArray.create(Float32Array, 1, chunkSize, spheres ? spheres.groupBuffer.ref.value : initialCount);
|
||||
|
||||
return {
|
||||
add: (x: number, y: number, z: number, group: number) => {
|
||||
const offset = centers.elementCount;
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
caAdd3(centers, x, y, z);
|
||||
caAdd2(mappings, quadMapping[i * 2], quadMapping[i * 2 + 1]);
|
||||
caAdd(groups, group);
|
||||
}
|
||||
caAdd3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]);
|
||||
caAdd3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]);
|
||||
caAdd3(centers, x, y, z);
|
||||
caAdd(groups, group);
|
||||
},
|
||||
getSpheres: () => {
|
||||
const cb = ChunkedArray.compact(centers, true) as Float32Array;
|
||||
const mb = ChunkedArray.compact(mappings, true) as Float32Array;
|
||||
const ib = ChunkedArray.compact(indices, true) as Uint32Array;
|
||||
const gb = ChunkedArray.compact(groups, true) as Float32Array;
|
||||
return Spheres.create(cb, mb, ib, gb, centers.elementCount / 4, spheres);
|
||||
return Spheres.create(cb, gb, centers.elementCount, spheres);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Theme } from '../../../mol-theme/theme';
|
||||
import { SpheresValues } from '../../../mol-gl/renderable/spheres';
|
||||
import { createColors } from '../color-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { TextureImage, calculateInvariantBoundingSphere, calculateTransformBoundingSphere, createTextureImage } from '../../../mol-gl/renderable/util';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { createSizes, getMaxSize } from '../size-data';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
@@ -23,7 +23,7 @@ import { createEmptyTransparency } from '../transparency-data';
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { GroupMapping, createGroupMapping } from '../../util';
|
||||
import { createEmptyClipping } from '../clipping-data';
|
||||
import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { createEmptySubstance } from '../substance-data';
|
||||
|
||||
@@ -35,10 +35,6 @@ export interface Spheres {
|
||||
|
||||
/** Center buffer as array of xyz values wrapped in a value cell */
|
||||
readonly centerBuffer: ValueCell<Float32Array>,
|
||||
/** Mapping buffer as array of xy values wrapped in a value cell */
|
||||
readonly mappingBuffer: ValueCell<Float32Array>,
|
||||
/** Index buffer as array of center index triplets wrapped in a value cell */
|
||||
readonly indexBuffer: ValueCell<Uint32Array>,
|
||||
/** Group buffer as array of group ids for each vertex wrapped in a value cell */
|
||||
readonly groupBuffer: ValueCell<Float32Array>,
|
||||
|
||||
@@ -48,45 +44,51 @@ export interface Spheres {
|
||||
readonly groupMapping: GroupMapping
|
||||
|
||||
setBoundingSphere(boundingSphere: Sphere3D): void
|
||||
|
||||
shaderData: Spheres.ShaderData
|
||||
}
|
||||
|
||||
export namespace Spheres {
|
||||
export function create(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
|
||||
export interface ShaderData {
|
||||
readonly positionGroup: ValueCell<TextureImage<Float32Array>>
|
||||
readonly texDim: ValueCell<Vec2>
|
||||
update(): void
|
||||
}
|
||||
|
||||
export function create(centers: Float32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
|
||||
return spheres ?
|
||||
update(centers, mappings, indices, groups, sphereCount, spheres) :
|
||||
fromArrays(centers, mappings, indices, groups, sphereCount);
|
||||
update(centers, groups, sphereCount, spheres) :
|
||||
fromArrays(centers, groups, sphereCount);
|
||||
}
|
||||
|
||||
export function createEmpty(spheres?: Spheres): Spheres {
|
||||
const cb = spheres ? spheres.centerBuffer.ref.value : new Float32Array(0);
|
||||
const mb = spheres ? spheres.mappingBuffer.ref.value : new Float32Array(0);
|
||||
const ib = spheres ? spheres.indexBuffer.ref.value : new Uint32Array(0);
|
||||
const gb = spheres ? spheres.groupBuffer.ref.value : new Float32Array(0);
|
||||
return create(cb, mb, ib, gb, 0, spheres);
|
||||
return create(cb, gb, 0, spheres);
|
||||
}
|
||||
|
||||
function hashCode(spheres: Spheres) {
|
||||
return hashFnv32a([
|
||||
spheres.sphereCount,
|
||||
spheres.centerBuffer.ref.version, spheres.mappingBuffer.ref.version,
|
||||
spheres.indexBuffer.ref.version, spheres.groupBuffer.ref.version
|
||||
spheres.centerBuffer.ref.version,
|
||||
spheres.groupBuffer.ref.version
|
||||
]);
|
||||
}
|
||||
|
||||
function fromArrays(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number): Spheres {
|
||||
|
||||
function fromArrays(centers: Float32Array, groups: Float32Array, sphereCount: number): Spheres {
|
||||
const boundingSphere = Sphere3D();
|
||||
let groupMapping: GroupMapping;
|
||||
|
||||
let currentHash = -1;
|
||||
let currentGroup = -1;
|
||||
|
||||
const positionGroup = ValueCell.create(createTextureImage(1, 4, Float32Array));
|
||||
const texDim = ValueCell.create(Vec2.create(0, 0));
|
||||
|
||||
const spheres = {
|
||||
kind: 'spheres' as const,
|
||||
sphereCount,
|
||||
centerBuffer: ValueCell.create(centers),
|
||||
mappingBuffer: ValueCell.create(mappings),
|
||||
indexBuffer: ValueCell.create(indices),
|
||||
groupBuffer: ValueCell.create(groups),
|
||||
get boundingSphere() {
|
||||
const newHash = hashCode(spheres);
|
||||
@@ -107,30 +109,49 @@ export namespace Spheres {
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere);
|
||||
currentHash = hashCode(spheres);
|
||||
}
|
||||
},
|
||||
shaderData: {
|
||||
positionGroup,
|
||||
texDim,
|
||||
update() {
|
||||
const pgt = createTextureImage(spheres.sphereCount, 4, Float32Array, positionGroup.ref.value.array);
|
||||
setPositionGroup(pgt, spheres.centerBuffer.ref.value, spheres.groupBuffer.ref.value, spheres.sphereCount);
|
||||
ValueCell.update(positionGroup, pgt);
|
||||
ValueCell.update(texDim, Vec2.set(texDim.ref.value, pgt.width, pgt.height));
|
||||
}
|
||||
},
|
||||
};
|
||||
return spheres;
|
||||
}
|
||||
|
||||
function update(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
|
||||
if (sphereCount > spheres.sphereCount) {
|
||||
ValueCell.update(spheres.mappingBuffer, mappings);
|
||||
ValueCell.update(spheres.indexBuffer, indices);
|
||||
}
|
||||
function update(centers: Float32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
|
||||
spheres.sphereCount = sphereCount;
|
||||
ValueCell.update(spheres.centerBuffer, centers);
|
||||
ValueCell.update(spheres.groupBuffer, groups);
|
||||
spheres.shaderData.update();
|
||||
return spheres;
|
||||
}
|
||||
|
||||
function setPositionGroup(out: TextureImage<Float32Array>, centers: Float32Array, groups: Float32Array, count: number) {
|
||||
const { array } = out;
|
||||
for (let i = 0; i < count; ++i) {
|
||||
array[i * 4 + 0] = centers[i * 3 + 0];
|
||||
array[i * 4 + 1] = centers[i * 3 + 1];
|
||||
array[i * 4 + 2] = centers[i * 3 + 2];
|
||||
array[i * 4 + 3] = groups[i];
|
||||
}
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
|
||||
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
|
||||
approximate: PD.Boolean(false, { ...BaseGeometry.ShadingCategory, description: 'Faster rendering, but has artifacts.' }),
|
||||
alphaThickness: PD.Numeric(0, { min: 0, max: 20, step: 1 }, { ...BaseGeometry.ShadingCategory, description: 'If not zero, adjusts alpha for radius.' }),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
};
|
||||
@@ -149,7 +170,7 @@ export namespace Spheres {
|
||||
};
|
||||
|
||||
function createPositionIterator(spheres: Spheres, transform: TransformData): LocationIterator {
|
||||
const groupCount = spheres.sphereCount * 4;
|
||||
const groupCount = spheres.sphereCount;
|
||||
const instanceCount = transform.instanceCount.ref.value;
|
||||
const location = PositionLocation();
|
||||
const p = location.position;
|
||||
@@ -163,7 +184,7 @@ export namespace Spheres {
|
||||
}
|
||||
return location;
|
||||
};
|
||||
return LocationIterator(groupCount, instanceCount, 4, getLocation);
|
||||
return LocationIterator(groupCount, instanceCount, 1, getLocation);
|
||||
}
|
||||
|
||||
function createValues(spheres: Spheres, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): SpheresValues {
|
||||
@@ -180,19 +201,20 @@ export namespace Spheres {
|
||||
const material = createEmptySubstance();
|
||||
const clipping = createEmptyClipping();
|
||||
|
||||
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
|
||||
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 6, groupCount, instanceCount };
|
||||
|
||||
const padding = spheres.boundingSphere.radius ? getMaxSize(size) * props.sizeFactor : 0;
|
||||
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
|
||||
|
||||
spheres.shaderData.update();
|
||||
|
||||
return {
|
||||
dGeometryType: ValueCell.create('spheres'),
|
||||
|
||||
aPosition: spheres.centerBuffer,
|
||||
aMapping: spheres.mappingBuffer,
|
||||
aGroup: spheres.groupBuffer,
|
||||
elements: spheres.indexBuffer,
|
||||
uTexDim: spheres.shaderData.texDim,
|
||||
tPositionGroup: spheres.shaderData.positionGroup,
|
||||
|
||||
boundingSphere: ValueCell.create(boundingSphere),
|
||||
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
|
||||
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
|
||||
@@ -211,11 +233,16 @@ export namespace Spheres {
|
||||
uSizeFactor: ValueCell.create(props.sizeFactor),
|
||||
uDoubleSided: ValueCell.create(props.doubleSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
dSolidInterior: ValueCell.create(props.solidInterior),
|
||||
dApproximate: ValueCell.create(props.approximate),
|
||||
uAlphaThickness: ValueCell.create(props.alphaThickness),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
|
||||
centerBuffer: spheres.centerBuffer,
|
||||
groupBuffer: spheres.groupBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,9 +257,11 @@ export namespace Spheres {
|
||||
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor);
|
||||
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
|
||||
ValueCell.updateIfChanged(values.dApproximate, props.approximate);
|
||||
ValueCell.updateIfChanged(values.uAlphaThickness, props.alphaThickness);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export namespace TextureMesh {
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
@@ -215,7 +215,7 @@ export namespace TextureMesh {
|
||||
dFlatShaded: ValueCell.create(props.flatShaded),
|
||||
dFlipSided: ValueCell.create(props.flipSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
|
||||
@@ -236,7 +236,7 @@ export namespace TextureMesh {
|
||||
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
|
||||
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
|
||||
|
||||
@@ -762,6 +762,7 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
|
||||
isContextLost: function () { return false; },
|
||||
drawingBufferWidth: width,
|
||||
drawingBufferHeight: height,
|
||||
drawingBufferColorSpace: 'srgb',
|
||||
blendColor: function () { },
|
||||
blendEquation: function () { },
|
||||
blendEquationSeparate: function () { },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -17,10 +17,11 @@ export function getGLContext(width: number, height: number) {
|
||||
return createContext(gl);
|
||||
}
|
||||
|
||||
export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean }) {
|
||||
export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean, textureFloat?: boolean }) {
|
||||
try {
|
||||
const ctx = getGLContext(width, height);
|
||||
if (requiredExtensions?.fragDepth && !ctx.extensions.fragDepth) return;
|
||||
if (requiredExtensions?.textureFloat && !ctx.extensions.textureFloat) return;
|
||||
return ctx;
|
||||
} catch (e) {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ export function createSpheres() {
|
||||
}
|
||||
|
||||
describe('spheres', () => {
|
||||
const ctx = tryGetGLContext(32, 32, { fragDepth: true });
|
||||
const ctx = tryGetGLContext(32, 32, { fragDepth: true, textureFloat: true });
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -25,7 +25,7 @@ export const CylindersSchema = {
|
||||
padding: ValueSpec('number'),
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
dSolidInterior: DefineSpec('boolean'),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -40,7 +40,7 @@ export const DirectVolumeSchema = {
|
||||
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
|
||||
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
};
|
||||
export type DirectVolumeSchema = typeof DirectVolumeSchema
|
||||
export type DirectVolumeValues = Values<DirectVolumeSchema>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -22,7 +22,7 @@ export const MeshSchema = {
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
|
||||
@@ -136,6 +136,7 @@ export const GlobalUniformSchema = {
|
||||
uCameraDir: UniformSpec('v3'),
|
||||
uNear: UniformSpec('f'),
|
||||
uFar: UniformSpec('f'),
|
||||
uFog: UniformSpec('b'),
|
||||
uFogNear: UniformSpec('f'),
|
||||
uFogFar: UniformSpec('f'),
|
||||
uFogColor: UniformSpec('v3'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -7,26 +7,30 @@
|
||||
import { Renderable, RenderableState, createRenderable } from '../renderable';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { createGraphicsRenderItem, GraphicsRenderVariant } from '../webgl/render-item';
|
||||
import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec } from './schema';
|
||||
import { GlobalUniformSchema, BaseSchema, Values, InternalSchema, SizeSchema, InternalValues, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec, TextureSpec } from './schema';
|
||||
import { SpheresShaderCode } from '../shader-code';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
|
||||
export const SpheresSchema = {
|
||||
...BaseSchema,
|
||||
...SizeSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
aMapping: AttributeSpec('float32', 2, 0),
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
uTexDim: UniformSpec('v2'),
|
||||
tPositionGroup: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
|
||||
padding: ValueSpec('number'),
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
dSolidInterior: DefineSpec('boolean'),
|
||||
dApproximate: DefineSpec('boolean'),
|
||||
uAlphaThickness: UniformSpec('f'),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
|
||||
centerBuffer: ValueSpec('float32'),
|
||||
groupBuffer: ValueSpec('float32'),
|
||||
};
|
||||
export type SpheresSchema = typeof SpheresSchema
|
||||
export type SpheresValues = Values<SpheresSchema>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -22,7 +22,7 @@ export const TextureMeshSchema = {
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
uBumpAmplitude: UniformSpec('f', 'material'),
|
||||
|
||||
@@ -49,23 +49,29 @@ const DefaultPrintImageOptions = {
|
||||
pixelated: false,
|
||||
id: 'molstar.debug.image',
|
||||
normalize: false,
|
||||
useCanvas: false,
|
||||
};
|
||||
export type PrintImageOptions = typeof DefaultPrintImageOptions
|
||||
|
||||
export function printTextureImage(textureImage: TextureImage<any>, options: Partial<PrintImageOptions> = {}) {
|
||||
|
||||
const { array, width, height } = textureImage;
|
||||
const itemSize = array.length / (width * height);
|
||||
const data = new Uint8ClampedArray(width * height * 4);
|
||||
const [min, max] = arrayMinMax(array);
|
||||
if (itemSize === 1) {
|
||||
data.fill(255);
|
||||
for (let y = 0; y < height; ++y) {
|
||||
for (let x = 0; x < width; ++x) {
|
||||
data[(y * width + x) * 4 + 3] = array[y * width + x];
|
||||
const i = y * width + x;
|
||||
if (options.normalize) {
|
||||
data[i * 4 + 0] = ((array[i] - min) / (max - min)) * 255;
|
||||
} else {
|
||||
data[i * 4 + 0] = array[i] * 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (itemSize === 4) {
|
||||
if (options.normalize) {
|
||||
const [min, max] = arrayMinMax(array);
|
||||
for (let i = 0, il = width * height * 4; i < il; i += 4) {
|
||||
data[i] = ((array[i] - min) / (max - min)) * 255;
|
||||
data[i + 1] = ((array[i + 1] - min) / (max - min)) * 255;
|
||||
@@ -87,14 +93,6 @@ let tmpContainer: HTMLDivElement;
|
||||
|
||||
export function printImageData(imageData: ImageData, options: Partial<PrintImageOptions> = {}) {
|
||||
const o = { ...DefaultPrintImageOptions, ...options };
|
||||
const canvas = tmpCanvas || document.createElement('canvas');
|
||||
tmpCanvas = canvas;
|
||||
canvas.width = imageData.width;
|
||||
canvas.height = imageData.height;
|
||||
const ctx = tmpCanvasCtx || canvas.getContext('2d');
|
||||
tmpCanvasCtx = ctx;
|
||||
if (!ctx) throw new Error('Could not create canvas 2d context');
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
if (!tmpContainer) {
|
||||
tmpContainer = document.createElement('div');
|
||||
@@ -106,23 +104,52 @@ export function printImageData(imageData: ImageData, options: Partial<PrintImage
|
||||
document.body.appendChild(tmpContainer);
|
||||
}
|
||||
|
||||
canvas.toBlob(imgBlob => {
|
||||
const objectURL = URL.createObjectURL(imgBlob!);
|
||||
const existingImg = document.getElementById(o.id) as HTMLImageElement;
|
||||
const img = existingImg || document.createElement('img');
|
||||
img.id = o.id;
|
||||
img.src = objectURL;
|
||||
img.style.width = imageData.width * o.scale + 'px';
|
||||
img.style.height = imageData.height * o.scale + 'px';
|
||||
if (o.useCanvas) {
|
||||
const existingCanvas = document.getElementById(o.id) as HTMLCanvasElement;
|
||||
const outCanvas = existingCanvas || document.createElement('canvas');
|
||||
outCanvas.width = imageData.width;
|
||||
outCanvas.height = imageData.height;
|
||||
const outCtx = outCanvas.getContext('2d');
|
||||
if (!outCtx) throw new Error('Could not create canvas 2d context');
|
||||
outCtx.putImageData(imageData, 0, 0);
|
||||
outCanvas.id = o.id;
|
||||
outCanvas.style.width = imageData.width * o.scale + 'px';
|
||||
outCanvas.style.height = imageData.height * o.scale + 'px';
|
||||
if (o.pixelated) {
|
||||
// not supported in Firefox and IE
|
||||
img.style.imageRendering = 'pixelated';
|
||||
outCanvas.style.imageRendering = 'pixelated';
|
||||
}
|
||||
img.style.position = 'relative';
|
||||
img.style.border = 'solid grey';
|
||||
img.style.pointerEvents = 'none';
|
||||
if (!existingImg) tmpContainer.appendChild(img);
|
||||
}, 'image/png');
|
||||
outCanvas.style.position = 'relative';
|
||||
outCanvas.style.border = 'solid grey';
|
||||
outCanvas.style.pointerEvents = 'none';
|
||||
if (!existingCanvas) tmpContainer.appendChild(outCanvas);
|
||||
} else {
|
||||
const canvas = tmpCanvas || document.createElement('canvas');
|
||||
tmpCanvas = canvas;
|
||||
canvas.width = imageData.width;
|
||||
canvas.height = imageData.height;
|
||||
const ctx = tmpCanvasCtx || canvas.getContext('2d');
|
||||
tmpCanvasCtx = ctx;
|
||||
if (!ctx) throw new Error('Could not create canvas 2d context');
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
canvas.toBlob(imgBlob => {
|
||||
const objectURL = URL.createObjectURL(imgBlob!);
|
||||
const existingImg = document.getElementById(o.id) as HTMLImageElement;
|
||||
const img = existingImg || document.createElement('img');
|
||||
img.id = o.id;
|
||||
img.src = objectURL;
|
||||
img.style.width = imageData.width * o.scale + 'px';
|
||||
img.style.height = imageData.height * o.scale + 'px';
|
||||
if (o.pixelated) {
|
||||
// not supported in Firefox and IE
|
||||
img.style.imageRendering = 'pixelated';
|
||||
}
|
||||
img.style.position = 'relative';
|
||||
img.style.border = 'solid grey';
|
||||
img.style.pointerEvents = 'none';
|
||||
if (!existingImg) tmpContainer.appendChild(img);
|
||||
}, 'image/png');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -213,6 +213,7 @@ namespace Renderer {
|
||||
uCameraDir: ValueCell.create(cameraDir),
|
||||
uNear: ValueCell.create(1),
|
||||
uFar: ValueCell.create(10000),
|
||||
uFog: ValueCell.create(true),
|
||||
uFogNear: ValueCell.create(1),
|
||||
uFogFar: ValueCell.create(10000),
|
||||
uFogColor: ValueCell.create(bgColor),
|
||||
@@ -425,7 +426,8 @@ namespace Renderer {
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) {
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && !xrayShaded) {
|
||||
renderObject(r, 'depth', Flag.None);
|
||||
}
|
||||
}
|
||||
@@ -443,7 +445,8 @@ namespace Renderer {
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) {
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || xrayShaded) {
|
||||
renderObject(r, 'depth', Flag.None);
|
||||
}
|
||||
}
|
||||
@@ -593,7 +596,8 @@ 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 !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !xrayShaded) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
|
||||
renderObject(r, 'colorWboit', Flag.None);
|
||||
}
|
||||
}
|
||||
@@ -611,7 +615,8 @@ 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 && alpha !== 0) || 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) {
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || xrayShaded) {
|
||||
renderObject(r, 'colorWboit', Flag.None);
|
||||
}
|
||||
}
|
||||
@@ -633,7 +638,8 @@ 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 !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !xrayShaded) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
@@ -659,7 +665,8 @@ 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 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value) {
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || xrayShaded) {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -8,6 +8,7 @@ import { ValueCell } from '../mol-util';
|
||||
import { idFactory } from '../mol-util/id-factory';
|
||||
import { WebGLExtensions } from './webgl/extensions';
|
||||
import { isWebGL2, GLRenderingContext } from './webgl/compat';
|
||||
import { assertUnreachable } from '../mol-util/type-helpers';
|
||||
|
||||
export type DefineKind = 'boolean' | 'string' | 'number'
|
||||
export type DefineType = boolean | string
|
||||
@@ -20,6 +21,8 @@ export interface ShaderExtensions {
|
||||
readonly fragDepth?: ShaderExtensionsValue
|
||||
readonly drawBuffers?: ShaderExtensionsValue
|
||||
readonly shaderTextureLod?: ShaderExtensionsValue
|
||||
readonly clipCullDistance?: ShaderExtensionsValue
|
||||
readonly conservativeDepth?: ShaderExtensionsValue
|
||||
}
|
||||
|
||||
type FragOutTypes = { [k in number]: 'vec4' | 'ivec4' }
|
||||
@@ -127,7 +130,7 @@ function unrollLoops(str: string) {
|
||||
return str.replace(reUnrollLoop, loopReplacer);
|
||||
}
|
||||
|
||||
function loopReplacer(match: string, start: string, end: string, snippet: string) {
|
||||
function loopReplacer(_match: string, start: string, end: string, snippet: string) {
|
||||
let out = '';
|
||||
for (let i = parseInt(start); i < parseInt(end); ++i) {
|
||||
out += snippet
|
||||
@@ -161,9 +164,10 @@ function ignoreDefine(name: string, variant: string, defines: ShaderDefines): bo
|
||||
} else {
|
||||
return [
|
||||
'dColorType', 'dUsePalette',
|
||||
'dLightCount',
|
||||
'dLightCount', 'dXrayShaded',
|
||||
'dOverpaintType', 'dOverpaint',
|
||||
'dSubstanceType', 'dSubstance',
|
||||
'dColorMarker',
|
||||
].includes(name);
|
||||
}
|
||||
return false;
|
||||
@@ -204,7 +208,6 @@ export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_v
|
||||
|
||||
import { image_vert } from './shader/image.vert';
|
||||
import { image_frag } from './shader/image.frag';
|
||||
import { assertUnreachable } from '../mol-util/type-helpers';
|
||||
export const ImageShaderCode = ShaderCode('image', image_vert, image_frag, { drawBuffers: 'optional' }, {}, ignoreDefineUnlit);
|
||||
|
||||
//
|
||||
@@ -313,6 +316,22 @@ function getGlsl300VertPrefix(extensions: WebGLExtensions, shaderExtensions: Sha
|
||||
prefix.push('#define requiredDrawBuffers');
|
||||
}
|
||||
}
|
||||
if (shaderExtensions.clipCullDistance) {
|
||||
if (extensions.clipCullDistance) {
|
||||
prefix.push('#extension GL_ANGLE_clip_cull_distance : enable');
|
||||
prefix.push('#define enabledClipCullDistance');
|
||||
} else if (shaderExtensions.clipCullDistance === 'required') {
|
||||
throw new Error(`required 'GL_ANGLE_clip_cull_distance' extension not available`);
|
||||
}
|
||||
}
|
||||
if (shaderExtensions.conservativeDepth) {
|
||||
if (extensions.conservativeDepth) {
|
||||
prefix.push('#extension GL_EXT_conservative_depth : enable');
|
||||
prefix.push('#define enabledConservativeDepth');
|
||||
} else if (shaderExtensions.conservativeDepth === 'required') {
|
||||
throw new Error(`required 'GL_EXT_conservative_depth' extension not available`);
|
||||
}
|
||||
}
|
||||
if (extensions.noNonInstancedActiveAttribs) {
|
||||
prefix.push('#define noNonInstancedActiveAttribs');
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ precision mediump sampler2D;
|
||||
uniform sampler2D tImage;
|
||||
uniform vec2 uImageScale;
|
||||
uniform vec2 uImageOffset;
|
||||
uniform float uBlur;
|
||||
uniform float uOpacity;
|
||||
uniform float uSaturation;
|
||||
uniform float uLightness;
|
||||
@@ -64,7 +65,11 @@ void main() {
|
||||
} else {
|
||||
coords = (gl_FragCoord.xy / uImageScale) + uImageOffset;
|
||||
}
|
||||
gl_FragColor = texture2D(tImage, vec2(coords.x, 1.0 - coords.y));
|
||||
#ifdef enabledShaderTextureLod
|
||||
gl_FragColor = texture2DLodEXT(tImage, vec2(coords.x, 1.0 - coords.y), uBlur * 8.0);
|
||||
#else
|
||||
gl_FragColor = texture2D(tImage, vec2(coords.x, 1.0 - coords.y));
|
||||
#endif
|
||||
gl_FragColor.a = uOpacity;
|
||||
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);
|
||||
#elif defined(dVariant_horizontalGradient)
|
||||
|
||||
145
src/mol-gl/shader/cas.frag.ts
Normal file
145
src/mol-gl/shader/cas.frag.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
export const cas_frag = `
|
||||
precision mediump float;
|
||||
precision mediump sampler2D;
|
||||
|
||||
uniform sampler2D tColor;
|
||||
uniform vec2 uTexSizeInv;
|
||||
|
||||
uniform float uSharpness;
|
||||
|
||||
// adapted from https://www.shadertoy.com/view/stXSWB
|
||||
|
||||
/*
|
||||
* FidelityFX Super Resolution scales up a low resolution
|
||||
* image, while adding fine detail.
|
||||
*
|
||||
* MIT Open License
|
||||
*
|
||||
* https://gpuopen.com/fsr
|
||||
*
|
||||
* Left: FSR processed
|
||||
* Right: Original texture, bilinear interpolation
|
||||
*
|
||||
* Mouse at top: Sharpness 0 stops (maximum)
|
||||
* Mouse at bottom: Sharpness 2 stops (minimum)
|
||||
*
|
||||
* It works in two passes-
|
||||
* EASU upsamples the image with a clamped Lanczos kernel.
|
||||
* RCAS sharpens the image at the target resolution.
|
||||
*
|
||||
* I needed to make a few changes to improve readability and
|
||||
* WebGL compatibility in an algorithm I don't fully understand.
|
||||
* Expect bugs.
|
||||
*
|
||||
* Shader not currently running for WebGL1 targets (eg. mobile Safari)
|
||||
*
|
||||
* There is kind of no point to using FSR in Shadertoy, as it renders buffers
|
||||
* at full target resolution. But this might be useful for WebGL based demos
|
||||
* running smaller-than-target render buffers.
|
||||
*
|
||||
* For sharpening with a full resolution render buffer,
|
||||
* FidelityFX CAS is a better option.
|
||||
* https://www.shadertoy.com/view/ftsXzM
|
||||
*
|
||||
* For readability and compatibility, these optimisations have been removed:
|
||||
* * Fast approximate inverse and inversesqrt
|
||||
* * textureGather fetches (not WebGL compatible)
|
||||
* * Multiplying by reciprocal instead of division
|
||||
*
|
||||
* Apologies to AMD for the numerous slowdowns and errors I have introduced.
|
||||
*
|
||||
*/
|
||||
|
||||
/***** RCAS *****/
|
||||
#define FSR_RCAS_LIMIT (0.25-(1.0/16.0))
|
||||
|
||||
// Input callback prototypes that need to be implemented by calling shader
|
||||
vec4 FsrRcasLoadF(vec2 p);
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
void FsrRcasCon(
|
||||
out float con,
|
||||
// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}.
|
||||
float sharpness
|
||||
) {
|
||||
// Transform from stops to linear value.
|
||||
con = exp2(-sharpness);
|
||||
}
|
||||
|
||||
vec3 FsrRcasF(
|
||||
vec2 ip, // Integer pixel position in output.
|
||||
float con
|
||||
) {
|
||||
// Constant generated by RcasSetup().
|
||||
// Algorithm uses minimal 3x3 pixel neighborhood.
|
||||
// b
|
||||
// d e f
|
||||
// h
|
||||
vec2 sp = vec2(ip);
|
||||
vec3 b = FsrRcasLoadF(sp + vec2( 0,-1)).rgb;
|
||||
vec3 d = FsrRcasLoadF(sp + vec2(-1, 0)).rgb;
|
||||
vec3 e = FsrRcasLoadF(sp).rgb;
|
||||
vec3 f = FsrRcasLoadF(sp + vec2( 1, 0)).rgb;
|
||||
vec3 h = FsrRcasLoadF(sp + vec2( 0, 1)).rgb;
|
||||
|
||||
// Luma times 2.
|
||||
float bL = b.g + .5 * (b.b + b.r);
|
||||
float dL = d.g + .5 * (d.b + d.r);
|
||||
float eL = e.g + .5 * (e.b + e.r);
|
||||
float fL = f.g + .5 * (f.b + f.r);
|
||||
float hL = h.g + .5 * (h.b + h.r);
|
||||
|
||||
// Noise detection.
|
||||
#ifdef dDenoise
|
||||
float nz = .25 * (bL + dL + fL + hL) - eL;
|
||||
nz=clamp(
|
||||
abs(nz)
|
||||
/(
|
||||
max(max(bL,dL),max(eL,max(fL,hL)))
|
||||
-min(min(bL,dL),min(eL,min(fL,hL)))
|
||||
),
|
||||
0., 1.
|
||||
);
|
||||
nz=1.-.5*nz;
|
||||
#endif
|
||||
|
||||
// Min and max of ring.
|
||||
vec3 mn4 = min(b, min(f, h));
|
||||
vec3 mx4 = max(b, max(f, h));
|
||||
|
||||
// Immediate constants for peak range.
|
||||
vec2 peakC = vec2(1., -4.);
|
||||
|
||||
// Limiters, these need to be high precision RCPs.
|
||||
vec3 hitMin = mn4 / (4. * mx4);
|
||||
vec3 hitMax = (peakC.x - mx4) / (4.* mn4 + peakC.y);
|
||||
vec3 lobeRGB = max(-hitMin, hitMax);
|
||||
float lobe = max(
|
||||
-FSR_RCAS_LIMIT,
|
||||
min(max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)), 0.)
|
||||
)*con;
|
||||
|
||||
// Apply noise removal.
|
||||
#ifdef dDenoise
|
||||
lobe *= nz;
|
||||
#endif
|
||||
|
||||
// Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes.
|
||||
return (lobe * (b + d + h + f) + e) / (4. * lobe + 1.);
|
||||
}
|
||||
|
||||
|
||||
vec4 FsrRcasLoadF(vec2 p) {
|
||||
return texture2D(tColor, p * uTexSizeInv);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Set up constants
|
||||
float con;
|
||||
FsrRcasCon(con, uSharpness);
|
||||
|
||||
// Perform RCAS pass
|
||||
vec3 col = FsrRcasF(gl_FragCoord.xy, con);
|
||||
|
||||
gl_FragColor = vec4(col, FsrRcasLoadF(gl_FragCoord.xy).a);
|
||||
}
|
||||
`;
|
||||
@@ -1,30 +1,32 @@
|
||||
export const apply_fog = `
|
||||
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
|
||||
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
|
||||
float preFogAlpha = gl_FragColor.a;
|
||||
if (!uTransparentBackground) {
|
||||
if (gl_FragColor.a < 1.0) {
|
||||
// transparent objects are blended with background color
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
// mix opaque objects with background color
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
|
||||
}
|
||||
} else {
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
if (uFog) {
|
||||
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
|
||||
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
|
||||
if (!uTransparentBackground) {
|
||||
if (gl_FragColor.a < 1.0) {
|
||||
// transparent objects are blended with background color
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
// opaque objects need to be pre-multiplied alpha
|
||||
// mix opaque objects with background color
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
|
||||
}
|
||||
} else {
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
if (gl_FragColor.a < 1.0) {
|
||||
// transparent objects are blended with background color
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
// opaque objects need to be pre-multiplied alpha
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
}
|
||||
#else
|
||||
// pre-multiplied alpha expected for transparent background
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
}
|
||||
#else
|
||||
// pre-multiplied alpha expected for transparent background
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -69,8 +69,10 @@ export const apply_light_color = `
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
#endif
|
||||
|
||||
#ifdef dXrayShaded
|
||||
#if defined(dXrayShaded_on)
|
||||
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
|
||||
#elif defined(dXrayShaded_inverted)
|
||||
gl_FragColor.a *= pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
|
||||
#endif
|
||||
|
||||
gl_FragColor.rgb *= uExposure;
|
||||
|
||||
@@ -65,7 +65,7 @@ export const common_clip = `
|
||||
|
||||
#if __VERSION__ == 100
|
||||
// 8-bit
|
||||
int bitwiseAnd(const in int a, const in int b) {
|
||||
int bitwiseAnd(in int a, in int b) {
|
||||
int d = 128;
|
||||
int result = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
|
||||
@@ -57,6 +57,7 @@ uniform float uNear;
|
||||
uniform float uFar;
|
||||
uniform float uIsOrtho;
|
||||
|
||||
uniform bool uFog;
|
||||
uniform float uFogNear;
|
||||
uniform float uFogFar;
|
||||
uniform vec3 uFogColor;
|
||||
|
||||
@@ -21,6 +21,10 @@ export const common = `
|
||||
#define dNeedsMarker
|
||||
#endif
|
||||
|
||||
#if defined(dXrayShaded_on) || defined(dXrayShaded_inverted)
|
||||
#define dXrayShaded
|
||||
#endif
|
||||
|
||||
#define MaskAll 0
|
||||
#define MaskOpaque 1
|
||||
#define MaskTransparent 2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
@@ -11,9 +11,8 @@ export const dpoit_write = `
|
||||
discard;
|
||||
}
|
||||
} else if (uRenderMask == MaskTransparent) {
|
||||
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
|
||||
vec2 coords = gl_FragCoord.xy / uDrawingBufferSize;
|
||||
if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(coords) || fragmentDepth > 0.99)) {
|
||||
if (preFogAlpha != 1.0 && fragmentDepth < getDepth(coords)) {
|
||||
#ifdef dTransparentBackfaces_off
|
||||
if (interior) discard;
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -12,8 +12,7 @@ export const wboit_write = `
|
||||
discard;
|
||||
}
|
||||
} else if (uRenderMask == MaskTransparent) {
|
||||
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
|
||||
if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize) || fragmentDepth > 0.99)) {
|
||||
if (preFogAlpha != 1.0 && fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
|
||||
#ifdef dTransparentBackfaces_off
|
||||
if (interior) discard;
|
||||
#endif
|
||||
|
||||
@@ -68,6 +68,7 @@ uniform int uGroupCount;
|
||||
uniform float uMetalness;
|
||||
uniform float uRoughness;
|
||||
|
||||
uniform bool uFog;
|
||||
uniform float uFogNear;
|
||||
uniform float uFogFar;
|
||||
uniform vec3 uFogColor;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -17,9 +17,9 @@ precision highp int;
|
||||
#include common_clip
|
||||
|
||||
uniform mat4 uInvView;
|
||||
uniform float uAlphaThickness;
|
||||
|
||||
varying float vRadius;
|
||||
varying float vRadiusSq;
|
||||
varying vec3 vPoint;
|
||||
varying vec3 vPointViewPosition;
|
||||
|
||||
@@ -37,7 +37,7 @@ bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal
|
||||
vec3 cameraSphereDir = mix(cameraSpherePos, rayOrigin - cameraSpherePos, uIsOrtho);
|
||||
|
||||
float B = dot(rayDirection, cameraSphereDir);
|
||||
float det = B * B + vRadiusSq - dot(cameraSphereDir, cameraSphereDir);
|
||||
float det = B * B + vRadius * vRadius - dot(cameraSphereDir, cameraSphereDir);
|
||||
|
||||
if (det < 0.0) return false;
|
||||
|
||||
@@ -83,21 +83,34 @@ bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal
|
||||
}
|
||||
|
||||
void main(void){
|
||||
vec3 modelPos;
|
||||
vec3 cameraPos;
|
||||
vec3 cameraNormal;
|
||||
float fragmentDepth;
|
||||
bool clipped = false;
|
||||
bool hit = SphereImpostor(modelPos, cameraPos, cameraNormal, interior, fragmentDepth);
|
||||
if (!hit) discard;
|
||||
|
||||
if (fragmentDepth < 0.0) discard;
|
||||
if (fragmentDepth > 1.0) discard;
|
||||
#ifdef dApproximate
|
||||
vec3 pointDir = -vPointViewPosition - vPoint;
|
||||
if (dot(pointDir, pointDir) > vRadius * vRadius) discard;
|
||||
vec3 vViewPosition = -vPointViewPosition;
|
||||
fragmentDepth = gl_FragCoord.z;
|
||||
#if !defined(dIgnoreLight) || defined(dXrayShaded)
|
||||
pointDir.z -= cos(length(pointDir) / vRadius);
|
||||
cameraNormal = -normalize(pointDir / vRadius);
|
||||
#endif
|
||||
interior = false;
|
||||
#else
|
||||
vec3 modelPos;
|
||||
vec3 cameraPos;
|
||||
bool hit = SphereImpostor(modelPos, cameraPos, cameraNormal, interior, fragmentDepth);
|
||||
if (!hit) discard;
|
||||
|
||||
vec3 vViewPosition = cameraPos;
|
||||
vec3 vModelPosition = modelPos;
|
||||
if (fragmentDepth < 0.0) discard;
|
||||
if (fragmentDepth > 1.0) discard;
|
||||
|
||||
gl_FragDepthEXT = fragmentDepth;
|
||||
gl_FragDepthEXT = fragmentDepth;
|
||||
|
||||
vec3 vModelPosition = modelPos;
|
||||
vec3 vViewPosition = cameraPos;
|
||||
#endif
|
||||
|
||||
#include clip_pixel
|
||||
#include assign_material_color
|
||||
@@ -120,6 +133,10 @@ void main(void){
|
||||
vec3 normal = -cameraNormal;
|
||||
#include apply_light_color
|
||||
|
||||
if (uRenderMask == MaskTransparent && uAlphaThickness > 0.0) {
|
||||
gl_FragColor.a *= min(1.0, vRadius / uAlphaThickness);
|
||||
}
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
@@ -17,65 +17,63 @@ precision highp int;
|
||||
|
||||
uniform mat4 uModelView;
|
||||
uniform mat4 uInvProjection;
|
||||
uniform float uIsOrtho;
|
||||
|
||||
uniform vec2 uTexDim;
|
||||
uniform sampler2D tPositionGroup;
|
||||
|
||||
attribute vec3 aPosition;
|
||||
attribute vec2 aMapping;
|
||||
attribute mat4 aTransform;
|
||||
attribute float aInstance;
|
||||
attribute float aGroup;
|
||||
|
||||
varying float vRadius;
|
||||
varying float vRadiusSq;
|
||||
varying vec3 vPoint;
|
||||
varying vec3 vPointViewPosition;
|
||||
|
||||
#include matrix_scale
|
||||
|
||||
const mat4 D = mat4(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, -1.0
|
||||
);
|
||||
|
||||
/**
|
||||
* Compute point size and center using the technique described in:
|
||||
* "GPU-Based Ray-Casting of Quadratic Surfaces" http://dl.acm.org/citation.cfm?id=2386396
|
||||
* by Christian Sigg, Tim Weyrich, Mario Botsch, Markus Gross.
|
||||
* Bounding rectangle of a clipped, perspective-projected 3D Sphere.
|
||||
* Michael Mara, Morgan McGuire. 2013
|
||||
*
|
||||
* Specialization by Arseny Kapoulkine, MIT License Copyright (c) 2018
|
||||
* https://github.com/zeux/niagara
|
||||
*/
|
||||
void quadraticProjection(const in float radius, const in vec3 position){
|
||||
vec2 xbc, ybc;
|
||||
void sphereProjection(const in vec3 p, const in float r, const in vec2 mapping) {
|
||||
vec3 pr = p * r;
|
||||
float pzr2 = p.z * p.z - r * r;
|
||||
|
||||
mat4 T = mat4(
|
||||
radius, 0.0, 0.0, 0.0,
|
||||
0.0, radius, 0.0, 0.0,
|
||||
0.0, 0.0, radius, 0.0,
|
||||
position.x, position.y, position.z, 1.0
|
||||
);
|
||||
float vx = sqrt(p.x * p.x + pzr2);
|
||||
float minx = ((vx * p.x - pr.z) / (vx * p.z + pr.x)) * uProjection[0][0];
|
||||
float maxx = ((vx * p.x + pr.z) / (vx * p.z - pr.x)) * uProjection[0][0];
|
||||
|
||||
mat4 R = transpose4(uProjection * uModelView * aTransform * T);
|
||||
float A = dot(R[3], D * R[3]);
|
||||
float B = -2.0 * dot(R[0], D * R[3]);
|
||||
float C = dot(R[0], D * R[0]);
|
||||
xbc[0] = (-B - sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
|
||||
xbc[1] = (-B + sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
|
||||
float sx = abs(xbc[0] - xbc[1]) * 0.5;
|
||||
float vy = sqrt(p.y * p.y + pzr2);
|
||||
float miny = ((vy * p.y - pr.z) / (vy * p.z + pr.y)) * uProjection[1][1];
|
||||
float maxy = ((vy * p.y + pr.z) / (vy * p.z - pr.y)) * uProjection[1][1];
|
||||
|
||||
A = dot(R[3], D * R[3]);
|
||||
B = -2.0 * dot(R[1], D * R[3]);
|
||||
C = dot(R[1], D * R[1]);
|
||||
ybc[0] = (-B - sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
|
||||
ybc[1] = (-B + sqrt(B * B - 4.0 * A * C)) / (2.0 * A);
|
||||
float sy = abs(ybc[0] - ybc[1]) * 0.5;
|
||||
|
||||
gl_Position.xy = vec2(0.5 * (xbc.x + xbc.y), 0.5 * (ybc.x + ybc.y));
|
||||
gl_Position.xy -= aMapping * vec2(sx, sy);
|
||||
gl_Position.xy = vec2(maxx + minx, maxy + miny) * -0.5;
|
||||
gl_Position.xy -= mapping * vec2(maxx - minx, maxy - miny) * 0.5;
|
||||
gl_Position.xy *= gl_Position.w;
|
||||
}
|
||||
|
||||
|
||||
void main(void){
|
||||
#include assign_group
|
||||
vec2 mapping = vec2(1.0, 1.0); // vertices 2 and 5
|
||||
#if __VERSION__ == 100
|
||||
int m = imod(VertexID, 6);
|
||||
#else
|
||||
int m = VertexID % 6;
|
||||
#endif
|
||||
if (m == 0) {
|
||||
mapping = vec2(-1.0, 1.0);
|
||||
} else if (m == 1 || m == 3) {
|
||||
mapping = vec2(-1.0, -1.0);
|
||||
} else if (m == 4) {
|
||||
mapping = vec2(1.0, -1.0);
|
||||
}
|
||||
|
||||
vec4 positionGroup = readFromTexture(tPositionGroup, VertexID / 6, uTexDim);
|
||||
vec3 position = positionGroup.rgb;
|
||||
float group = positionGroup.a;
|
||||
|
||||
#include assign_color_varying
|
||||
#include assign_marker_varying
|
||||
#include assign_clipping_varying
|
||||
@@ -83,13 +81,24 @@ void main(void){
|
||||
|
||||
vRadius = size * matrixScale(uModelView);
|
||||
|
||||
vec4 position4 = vec4(aPosition, 1.0);
|
||||
vec4 position4 = vec4(position, 1.0);
|
||||
vec4 mvPosition = uModelView * aTransform * position4;
|
||||
|
||||
gl_Position = uProjection * vec4(mvPosition.xyz, 1.0);
|
||||
quadraticProjection(size, aPosition);
|
||||
#ifdef dApproximate
|
||||
vec4 mvCorner = vec4(mvPosition.xyz, 1.0);
|
||||
mvCorner.xy += mapping * vRadius;
|
||||
gl_Position = uProjection * mvCorner;
|
||||
#else
|
||||
if (uIsOrtho == 1.0) {
|
||||
vec4 mvCorner = vec4(mvPosition.xyz, 1.0);
|
||||
mvCorner.xy += mapping * vRadius;
|
||||
gl_Position = uProjection * mvCorner;
|
||||
} else {
|
||||
gl_Position = uProjection * vec4(mvPosition.xyz, 1.0);
|
||||
sphereProjection(mvPosition.xyz, vRadius, mapping);
|
||||
}
|
||||
#endif
|
||||
|
||||
vRadiusSq = vRadius * vRadius;
|
||||
vec4 vPoint4 = uInvProjection * gl_Position;
|
||||
vPoint = vPoint4.xyz / vPoint4.w;
|
||||
vPointViewPosition = -mvPosition.xyz / mvPosition.w;
|
||||
|
||||
@@ -114,7 +114,7 @@ void main(void) {
|
||||
vec2 selfPackedDepth = packUnitIntervalToRG(selfDepth);
|
||||
|
||||
if (isBackground(selfDepth)) {
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(0.0), selfPackedDepth);
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(1.0), selfPackedDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ export interface Buffer {
|
||||
destroy: () => void
|
||||
}
|
||||
|
||||
function getBuffer(gl: GLRenderingContext) {
|
||||
export function getBuffer(gl: GLRenderingContext) {
|
||||
const buffer = gl.createBuffer();
|
||||
if (buffer === null) {
|
||||
throw new Error('Could not create WebGL buffer');
|
||||
|
||||
@@ -586,6 +586,123 @@ export function getProvokingVertex(gl: GLRenderingContext): COMPAT_provoking_ver
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://registry.khronos.org/webgl/extensions/WEBGL_clip_cull_distance/
|
||||
*/
|
||||
export interface COMPAT_clip_cull_distance {
|
||||
readonly MAX_CLIP_DISTANCES: number;
|
||||
readonly MAX_CULL_DISTANCES: number;
|
||||
readonly MAX_COMBINED_CLIP_AND_CULL_DISTANCES: number;
|
||||
|
||||
readonly CLIP_DISTANCE0: number;
|
||||
readonly CLIP_DISTANCE1: number;
|
||||
readonly CLIP_DISTANCE2: number;
|
||||
readonly CLIP_DISTANCE3: number;
|
||||
readonly CLIP_DISTANCE4: number;
|
||||
readonly CLIP_DISTANCE5: number;
|
||||
readonly CLIP_DISTANCE6: number;
|
||||
readonly CLIP_DISTANCE7: number;
|
||||
}
|
||||
|
||||
export function getClipCullDistance(gl: GLRenderingContext): COMPAT_clip_cull_distance | null {
|
||||
if (isWebGL2(gl)) {
|
||||
const ext = gl.getExtension('WEBGL_clip_cull_distance');
|
||||
if (ext) {
|
||||
return {
|
||||
MAX_CLIP_DISTANCES: ext.MAX_CLIP_DISTANCES_WEBGL,
|
||||
MAX_CULL_DISTANCES: ext.MAX_CULL_DISTANCES_WEBGL,
|
||||
MAX_COMBINED_CLIP_AND_CULL_DISTANCES: ext.MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL,
|
||||
|
||||
CLIP_DISTANCE0: ext.CLIP_DISTANCE0_WEBGL,
|
||||
CLIP_DISTANCE1: ext.CLIP_DISTANCE1_WEBGL,
|
||||
CLIP_DISTANCE2: ext.CLIP_DISTANCE2_WEBGL,
|
||||
CLIP_DISTANCE3: ext.CLIP_DISTANCE3_WEBGL,
|
||||
CLIP_DISTANCE4: ext.CLIP_DISTANCE4_WEBGL,
|
||||
CLIP_DISTANCE5: ext.CLIP_DISTANCE5_WEBGL,
|
||||
CLIP_DISTANCE6: ext.CLIP_DISTANCE6_WEBGL,
|
||||
CLIP_DISTANCE7: ext.CLIP_DISTANCE7_WEBGL
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://registry.khronos.org/webgl/extensions/EXT_conservative_depth/
|
||||
*/
|
||||
export interface COMPAT_conservative_depth {
|
||||
}
|
||||
|
||||
export function getConservativeDepth(gl: GLRenderingContext): COMPAT_conservative_depth | null {
|
||||
if (isWebGL2(gl)) {
|
||||
const ext = gl.getExtension('EXT_conservative_depth');
|
||||
if (ext) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://registry.khronos.org/webgl/extensions/WEBGL_stencil_texturing/
|
||||
*/
|
||||
export interface COMPAT_stencil_texturing {
|
||||
readonly DEPTH_STENCIL_TEXTURE_MODE: number;
|
||||
readonly STENCIL_INDEX: number;
|
||||
}
|
||||
|
||||
export function getStencilTexturing(gl: GLRenderingContext): COMPAT_stencil_texturing | null {
|
||||
if (isWebGL2(gl)) {
|
||||
const ext = gl.getExtension('WEBGL_stencil_texturing');
|
||||
if (ext) {
|
||||
return {
|
||||
DEPTH_STENCIL_TEXTURE_MODE: ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL,
|
||||
STENCIL_INDEX: ext.STENCIL_INDEX_WEBGL
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://registry.khronos.org/webgl/extensions/EXT_clip_control/
|
||||
*/
|
||||
export interface COMPAT_clip_control {
|
||||
readonly LOWER_LEFT: number;
|
||||
readonly UPPER_LEFT: number;
|
||||
|
||||
readonly NEGATIVE_ONE_TO_ONE: number;
|
||||
readonly ZERO_TO_ONE: number;
|
||||
|
||||
readonly CLIP_ORIGIN: number;
|
||||
readonly CLIP_DEPTH_MODE: number;
|
||||
|
||||
/**
|
||||
* @param origin must be LOWER_LEFT (default) or UPPER_LEFT.
|
||||
* @param depth must be NEGATIVE_ONE_TO_ONE (default) or ZERO_TO_ONE.
|
||||
*/
|
||||
clipControl(origin: number, depth: number): void
|
||||
}
|
||||
|
||||
export function getClipControl(gl: GLRenderingContext): COMPAT_clip_control | null {
|
||||
const ext = gl.getExtension('EXT_clip_control');
|
||||
if (ext) {
|
||||
return {
|
||||
LOWER_LEFT: ext.LOWER_LEFT_EXT,
|
||||
UPPER_LEFT: ext.UPPER_LEFT_EXT,
|
||||
|
||||
NEGATIVE_ONE_TO_ONE: ext.NEGATIVE_ONE_TO_ONE_EXT,
|
||||
ZERO_TO_ONE: ext.ZERO_TO_ONE_EXT,
|
||||
|
||||
CLIP_ORIGIN: ext.CLIP_ORIGIN_EXT,
|
||||
CLIP_DEPTH_MODE: ext.CLIP_DEPTH_MODE_EXT,
|
||||
|
||||
clipControl: ext.clipControlEXT.bind(ext)
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getNoNonInstancedActiveAttribs(gl: GLRenderingContext): boolean {
|
||||
if (!isWebGL2(gl)) return false;
|
||||
|
||||
|
||||
@@ -52,6 +52,16 @@ export function checkError(gl: GLRenderingContext) {
|
||||
}
|
||||
}
|
||||
|
||||
export function glEnumToString(gl: GLRenderingContext, value: number) {
|
||||
const keys: string[] = [];
|
||||
for (const key in gl) {
|
||||
if ((gl as any)[key] === value) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
return keys.length ? keys.join(' | ') : `0x${value.toString(16)}`;
|
||||
}
|
||||
|
||||
function unbindResources(gl: GLRenderingContext) {
|
||||
// bind null to all texture units
|
||||
const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
|
||||
@@ -227,7 +237,7 @@ export interface WebGLContext {
|
||||
|
||||
export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScale: number }> = {}): WebGLContext {
|
||||
const extensions = createExtensions(gl);
|
||||
const state = createState(gl);
|
||||
const state = createState(gl, extensions);
|
||||
const stats = createStats();
|
||||
const resources = createResources(gl, state, stats, extensions);
|
||||
const timer = createTimer(gl, extensions, stats);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject, getDisjointTimerQuery, COMPAT_disjoint_timer_query, getNoNonInstancedActiveAttribs, getDrawBuffersIndexed, COMPAT_draw_buffers_indexed, getParallelShaderCompile, COMPAT_parallel_shader_compile, getFboRenderMipmap, COMPAT_fboRenderMipmap, COMPAT_provoking_vertex, getProvokingVertex } from './compat';
|
||||
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject, getDisjointTimerQuery, COMPAT_disjoint_timer_query, getNoNonInstancedActiveAttribs, getDrawBuffersIndexed, COMPAT_draw_buffers_indexed, getParallelShaderCompile, COMPAT_parallel_shader_compile, getFboRenderMipmap, COMPAT_fboRenderMipmap, COMPAT_provoking_vertex, getProvokingVertex, COMPAT_clip_cull_distance, getClipCullDistance, COMPAT_conservative_depth, getConservativeDepth, COMPAT_stencil_texturing, getStencilTexturing, COMPAT_clip_control, getClipControl } from './compat';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
|
||||
export type WebGLExtensions = {
|
||||
@@ -30,6 +30,10 @@ export type WebGLExtensions = {
|
||||
parallelShaderCompile: COMPAT_parallel_shader_compile | null
|
||||
fboRenderMipmap: COMPAT_fboRenderMipmap | null
|
||||
provokingVertex: COMPAT_provoking_vertex | null
|
||||
clipCullDistance: COMPAT_clip_cull_distance | null
|
||||
conservativeDepth: COMPAT_conservative_depth | null
|
||||
stencilTexturing: COMPAT_stencil_texturing | null
|
||||
clipControl: COMPAT_clip_control | null
|
||||
|
||||
noNonInstancedActiveAttribs: boolean
|
||||
}
|
||||
@@ -126,6 +130,22 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
|
||||
if (isDebugMode && provokingVertex === null) {
|
||||
console.log('Could not find support for "provoking_vertex"');
|
||||
}
|
||||
const clipCullDistance = getClipCullDistance(gl);
|
||||
if (isDebugMode && clipCullDistance === null) {
|
||||
console.log('Could not find support for "clip_cull_distance"');
|
||||
}
|
||||
const conservativeDepth = getConservativeDepth(gl);
|
||||
if (isDebugMode && conservativeDepth === null) {
|
||||
console.log('Could not find support for "conservative_depth"');
|
||||
}
|
||||
const stencilTexturing = getStencilTexturing(gl);
|
||||
if (isDebugMode && stencilTexturing === null) {
|
||||
console.log('Could not find support for "stencil_texturing"');
|
||||
}
|
||||
const clipControl = getClipControl(gl);
|
||||
if (isDebugMode && clipControl === null) {
|
||||
console.log('Could not find support for "clipControl"');
|
||||
}
|
||||
|
||||
const noNonInstancedActiveAttribs = getNoNonInstancedActiveAttribs(gl);
|
||||
|
||||
@@ -152,6 +172,10 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
|
||||
parallelShaderCompile,
|
||||
fboRenderMipmap,
|
||||
provokingVertex,
|
||||
clipCullDistance,
|
||||
conservativeDepth,
|
||||
stencilTexturing,
|
||||
clipControl,
|
||||
|
||||
noNonInstancedActiveAttribs,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { GLRenderingContext } from './compat';
|
||||
import { WebGLExtensions } from './extensions';
|
||||
|
||||
export type WebGLState = {
|
||||
currentProgramId: number
|
||||
@@ -22,6 +23,7 @@ export type WebGLState = {
|
||||
* - `gl.SAMPLE_COVERAGE`: ANDing the fragment's coverage with the temporary coverage value
|
||||
* - `gl.SCISSOR_TEST`: scissor test that discards fragments that are outside of the scissor rectangle
|
||||
* - `gl.STENCIL_TEST`: stencil testing and updates to the stencil buffer
|
||||
* - `ext.CLIP_DISTANCE[0-7]`: clip distance 0 to 7 (with `ext` being `WEBGL_clip_cull_distance`)
|
||||
*/
|
||||
enable: (cap: number) => void
|
||||
/**
|
||||
@@ -35,6 +37,7 @@ export type WebGLState = {
|
||||
* - `gl.SAMPLE_COVERAGE`: ANDing the fragment's coverage with the temporary coverage value
|
||||
* - `gl.SCISSOR_TEST`: scissor test that discards fragments that are outside of the scissor rectangle
|
||||
* - `gl.STENCIL_TEST`: stencil testing and updates to the stencil buffer
|
||||
* - `ext.CLIP_DISTANCE[0-7]`: clip distance 0 to 7 (with `ext` being `WEBGL_clip_cull_distance`)
|
||||
*/
|
||||
disable: (cap: number) => void
|
||||
|
||||
@@ -85,10 +88,18 @@ export type WebGLState = {
|
||||
viewport: (x: number, y: number, width: number, height: number) => void
|
||||
scissor: (x: number, y: number, width: number, height: number) => void
|
||||
|
||||
/**
|
||||
* controls the clipping volume behavior
|
||||
* @param origin must be `ext.LOWER_LEFT` (default) or `ext.UPPER_LEFT`.
|
||||
* @param depth must be `ext.NEGATIVE_ONE_TO_ONE` (default) or `ext.ZERO_TO_ONE`.
|
||||
* with `ext` being `EXT_clip_control`
|
||||
*/
|
||||
clipControl?: (origin: number, depth: number) => void
|
||||
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export function createState(gl: GLRenderingContext): WebGLState {
|
||||
export function createState(gl: GLRenderingContext, e: WebGLExtensions): WebGLState {
|
||||
let enabledCapabilities: Record<number, boolean> = {};
|
||||
|
||||
let currentFrontFace = gl.getParameter(gl.FRONT_FACE);
|
||||
@@ -128,6 +139,9 @@ export function createState(gl: GLRenderingContext): WebGLState {
|
||||
let currentViewport: [number, number, number, number] = gl.getParameter(gl.VIEWPORT);
|
||||
let currentScissor: [number, number, number, number] = gl.getParameter(gl.SCISSOR_BOX);
|
||||
|
||||
let currentClipOrigin = e.clipControl ? gl.getParameter(e.clipControl.CLIP_ORIGIN) : -1;
|
||||
let currentClipDepthMode = e.clipControl ? gl.getParameter(e.clipControl.CLIP_DEPTH_MODE) : -1;
|
||||
|
||||
const clearVertexAttribsState = () => {
|
||||
for (let i = 0; i < maxVertexAttribs; ++i) {
|
||||
vertexAttribsState[i] = 0;
|
||||
@@ -378,6 +392,14 @@ export function createState(gl: GLRenderingContext): WebGLState {
|
||||
}
|
||||
},
|
||||
|
||||
clipControl: e.clipControl ? (origin: number, depth: number) => {
|
||||
if (origin !== currentClipOrigin || depth !== currentClipDepthMode) {
|
||||
e.clipControl!.clipControl(origin, depth);
|
||||
currentClipOrigin = origin;
|
||||
currentClipDepthMode = depth;
|
||||
}
|
||||
} : undefined,
|
||||
|
||||
reset: () => {
|
||||
enabledCapabilities = {};
|
||||
|
||||
@@ -420,6 +442,9 @@ export function createState(gl: GLRenderingContext): WebGLState {
|
||||
|
||||
currentViewport = gl.getParameter(gl.VIEWPORT);
|
||||
currentScissor = gl.getParameter(gl.SCISSOR_BOX);
|
||||
|
||||
currentClipOrigin = e.clipControl ? gl.getParameter(e.clipControl.CLIP_ORIGIN) : -1;
|
||||
currentClipDepthMode = e.clipControl ? gl.getParameter(e.clipControl.CLIP_DEPTH_MODE) : -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { isWebGL2, GLRenderingContext } from './compat';
|
||||
import { isPromiseLike, ValueOf } from '../../mol-util/type-helpers';
|
||||
import { WebGLExtensions } from './extensions';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { isPowerOfTwo } from '../../mol-math/misc';
|
||||
|
||||
const getNextTextureId = idFactory();
|
||||
|
||||
@@ -214,6 +215,7 @@ export interface Texture {
|
||||
* `define` or `load` without `sub` must have been called before.
|
||||
*/
|
||||
load: (image: TextureImage<any> | TextureVolume<any> | HTMLImageElement, sub?: boolean) => void
|
||||
mipmap: () => void
|
||||
bind: (id: TextureId) => void
|
||||
unbind: (id: TextureId) => void
|
||||
/** Use `layer` to attach a z-slice of a 3D texture */
|
||||
@@ -275,6 +277,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
|
||||
let width = 0, height = 0, depth = 0;
|
||||
let loadedData: undefined | TextureImage<any> | TextureVolume<any> | HTMLImageElement;
|
||||
let hasMipmap = false;
|
||||
let destroyed = false;
|
||||
|
||||
function define(_width: number, _height: number, _depth?: number) {
|
||||
@@ -337,6 +340,22 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
loadedData = data;
|
||||
}
|
||||
|
||||
function mipmap() {
|
||||
if (target !== gl.TEXTURE_2D) {
|
||||
throw new Error('mipmap only supported for 2d textures');
|
||||
}
|
||||
|
||||
if (isWebGL2(gl) || (isPowerOfTwo(width) && isPowerOfTwo(height))) {
|
||||
gl.bindTexture(target, texture);
|
||||
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
||||
gl.generateMipmap(target);
|
||||
gl.bindTexture(target, null);
|
||||
hasMipmap = true;
|
||||
} else {
|
||||
throw new Error('mipmap unsupported for non-power-of-two textures and webgl1');
|
||||
}
|
||||
}
|
||||
|
||||
function attachFramebuffer(framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) {
|
||||
framebuffer.bind();
|
||||
if (target === gl.TEXTURE_2D) {
|
||||
@@ -365,6 +384,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
|
||||
define,
|
||||
load,
|
||||
mipmap,
|
||||
bind: (id: TextureId) => {
|
||||
gl.activeTexture(gl.TEXTURE0 + id);
|
||||
gl.bindTexture(target, texture);
|
||||
@@ -392,6 +412,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
width = 0, height = 0, depth = 0; // set to zero to trigger resize
|
||||
define(_width, _height, _depth);
|
||||
if (loadedData) load(loadedData);
|
||||
if (hasMipmap) mipmap();
|
||||
},
|
||||
destroy: () => {
|
||||
if (destroyed) return;
|
||||
@@ -498,8 +519,8 @@ export function createCubeTexture(gl: GLRenderingContext, faces: CubeFaces, mipm
|
||||
if (loadedCount === 6) {
|
||||
if (!destroyed) {
|
||||
if (mipmaps) {
|
||||
gl.generateMipmap(target);
|
||||
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
||||
gl.generateMipmap(target);
|
||||
} else {
|
||||
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter);
|
||||
}
|
||||
@@ -532,6 +553,7 @@ export function createCubeTexture(gl: GLRenderingContext, faces: CubeFaces, mipm
|
||||
|
||||
define: () => {},
|
||||
load: () => {},
|
||||
mipmap: () => {},
|
||||
bind: (id: TextureId) => {
|
||||
gl.activeTexture(gl.TEXTURE0 + id);
|
||||
gl.bindTexture(target, texture);
|
||||
@@ -577,6 +599,7 @@ export function createNullTexture(gl?: GLRenderingContext): Texture {
|
||||
|
||||
define: () => {},
|
||||
load: () => {},
|
||||
mipmap: () => {},
|
||||
bind: (id: TextureId) => {
|
||||
if (gl) {
|
||||
gl.activeTexture(gl.TEXTURE0 + id);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -18,6 +18,9 @@ export type UniformKindValue = {
|
||||
'v2': Vec2; 'v2[]': number[]
|
||||
'v3': Vec3; 'v3[]': number[]
|
||||
'v4': Vec4; 'v4[]': number[]
|
||||
'iv2': Vec2; 'iv2[]': number[]
|
||||
'iv3': Vec3; 'iv3[]': number[]
|
||||
'iv4': Vec4; 'iv4[]': number[]
|
||||
'm3': Mat3; 'm3[]': number[]
|
||||
'm4': Mat4; 'm4[]': number[]
|
||||
't': number; 't[]': number[]
|
||||
@@ -36,6 +39,9 @@ export function getUniformType(gl: GLRenderingContext, kind: UniformKind) {
|
||||
case 'v2': case 'v2[]': return gl.FLOAT_VEC2;
|
||||
case 'v3': case 'v3[]': return gl.FLOAT_VEC3;
|
||||
case 'v4': case 'v4[]': return gl.FLOAT_VEC4;
|
||||
case 'iv2': case 'iv2[]': return gl.INT_VEC2;
|
||||
case 'iv3': case 'iv3[]': return gl.INT_VEC3;
|
||||
case 'iv4': case 'iv4[]': return gl.INT_VEC4;
|
||||
case 'm3': case 'm3[]': return gl.FLOAT_MAT3;
|
||||
case 'm4': case 'm4[]': return gl.FLOAT_MAT4;
|
||||
default: console.error(`unknown uniform kind '${kind}'`);
|
||||
@@ -56,6 +62,9 @@ function uniform1iv(gl: GLRenderingContext, location: number, value: any) { gl.u
|
||||
function uniform2fv(gl: GLRenderingContext, location: number, value: any) { gl.uniform2fv(location, value); }
|
||||
function uniform3fv(gl: GLRenderingContext, location: number, value: any) { gl.uniform3fv(location, value); }
|
||||
function uniform4fv(gl: GLRenderingContext, location: number, value: any) { gl.uniform4fv(location, value); }
|
||||
function uniform2iv(gl: GLRenderingContext, location: number, value: any) { gl.uniform2iv(location, value); }
|
||||
function uniform3iv(gl: GLRenderingContext, location: number, value: any) { gl.uniform3iv(location, value); }
|
||||
function uniform4iv(gl: GLRenderingContext, location: number, value: any) { gl.uniform4iv(location, value); }
|
||||
function uniformMatrix3fv(gl: GLRenderingContext, location: number, value: any) { gl.uniformMatrix3fv(location, false, value); }
|
||||
function uniformMatrix4fv(gl: GLRenderingContext, location: number, value: any) { gl.uniformMatrix4fv(location, false, value); }
|
||||
|
||||
@@ -68,6 +77,9 @@ function getUniformSetter(kind: UniformKind): UniformSetter {
|
||||
case 'v2': case 'v2[]': return uniform2fv;
|
||||
case 'v3': case 'v3[]': return uniform3fv;
|
||||
case 'v4': case 'v4[]': return uniform4fv;
|
||||
case 'iv2': case 'iv2[]': return uniform2iv;
|
||||
case 'iv3': case 'iv3[]': return uniform3iv;
|
||||
case 'iv4': case 'iv4[]': return uniform4iv;
|
||||
case 'm3': case 'm3[]': return uniformMatrix3fv;
|
||||
case 'm4': case 'm4[]': return uniformMatrix4fv;
|
||||
}
|
||||
|
||||
@@ -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.365, IHM 1.18, MA 1.4.4.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.379, IHM 1.23, MA 1.4.5.
|
||||
*
|
||||
* @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.365, IHM 1.18, MA 1.4.4.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.379, IHM 1.23, MA 1.4.5.
|
||||
*
|
||||
* @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 'CifCore' schema file. Dictionary versions: CifCore 3.1.0.
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.3.0.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -18,7 +18,7 @@ const Matrix = Schema.Matrix;
|
||||
export const CifCore_Schema = {
|
||||
/**
|
||||
* The CATEGORY of data items used to describe the parameters of
|
||||
* the crystal unit cell and their measurement.
|
||||
* the crystal unit cell.
|
||||
*/
|
||||
cell: {
|
||||
/**
|
||||
@@ -84,8 +84,8 @@ export const CifCore_Schema = {
|
||||
* _chemical_formula.analytical, *.structural and *.sum. For the
|
||||
* data item *.moiety the formula construction is broken up into
|
||||
* residues or moieties, i.e. groups of atoms that form a molecular
|
||||
* unit or molecular ion. The rules given below apply within each
|
||||
* moiety but different requirements apply to the way that moieties
|
||||
* unit or molecular ion. The rules given below apply within each
|
||||
* moiety, but different requirements apply to the way that moieties
|
||||
* are connected (see _chemical_formula.moiety).
|
||||
*
|
||||
* 1. Only recognized element symbols may be used.
|
||||
@@ -109,7 +109,7 @@ export const CifCore_Schema = {
|
||||
* depends on whether or not carbon is present. If carbon is
|
||||
* present, the order should be: C, then H, then the other
|
||||
* elements in alphabetical order of their symbol. If carbon is
|
||||
* not present, the elements are listed purely in alphabetic order
|
||||
* not present, the elements are listed purely in alphabetical order
|
||||
* of their symbol. This is the 'Hill' system used by Chemical
|
||||
* Abstracts. This ordering is used in _chemical_formula.moiety
|
||||
* and _chemical_formula.sum.
|
||||
@@ -165,7 +165,7 @@ export const CifCore_Schema = {
|
||||
* stored in this dictionary.
|
||||
*
|
||||
* The commonly used Hermann-Mauguin symbol determines the
|
||||
* space-group type uniquely but several different Hermann-Mauguin
|
||||
* space-group type uniquely, but several different Hermann-Mauguin
|
||||
* symbols may refer to the same space-group type. A
|
||||
* Hermann-Mauguin symbol contains information on the choice of
|
||||
* the basis, but not on the choice of origin.
|
||||
@@ -205,13 +205,13 @@ export const CifCore_Schema = {
|
||||
* Subscripts should appear without special symbols. Bars should
|
||||
* be given as negative signs before the numbers to which they
|
||||
* apply. The commonly used Hermann-Mauguin symbol determines the
|
||||
* space-group type uniquely but a given space-group type may
|
||||
* space-group type uniquely, but a given space-group type may
|
||||
* be described by more than one Hermann-Mauguin symbol. The
|
||||
* space-group type is best described using
|
||||
* _space_group.IT_number or _space_group.name_Schoenflies. The
|
||||
* full international Hermann-Mauguin symbol contains information
|
||||
* about the choice of basis for monoclinic and orthorhombic
|
||||
* space groups but does not give information about the choice
|
||||
* space groups, but does not give information about the choice
|
||||
* of origin. To define the setting uniquely use
|
||||
* _space_group.name_Hall, or list the symmetry operations
|
||||
* or generators.
|
||||
@@ -282,8 +282,8 @@ export const CifCore_Schema = {
|
||||
*/
|
||||
publ_flag: str,
|
||||
/**
|
||||
* The set of data items which specify the symmetry operation codes
|
||||
* which must be applied to the atom sites involved in the geometry angle.
|
||||
* Data item specifying the symmetry operation codes applied to the atom
|
||||
* sites involved in a specific geometric configuration.
|
||||
*
|
||||
* The symmetry code of each atom site as the symmetry-equivalent position
|
||||
* number 'n' and the cell translation number 'pqr'. These numbers are
|
||||
@@ -292,8 +292,9 @@ export const CifCore_Schema = {
|
||||
* The character string n_pqr is composed as follows:
|
||||
*
|
||||
* n refers to the symmetry operation that is applied to the
|
||||
* coordinates stored in _atom_site.fract_xyz. It must match a
|
||||
* number given in _symmetry_equiv.pos_site_id.
|
||||
* coordinates stored in _atom_site.fract_xyz. It must match
|
||||
* a number given in _space_group_symop.id (or one of its
|
||||
* aliases, such as _symmetry_equiv_pos_site_id).
|
||||
*
|
||||
* p, q and r refer to the translations that are subsequently
|
||||
* applied to the symmetry transformed coordinates to generate
|
||||
@@ -305,8 +306,8 @@ export const CifCore_Schema = {
|
||||
*/
|
||||
site_symmetry_1: str,
|
||||
/**
|
||||
* The set of data items which specify the symmetry operation codes
|
||||
* which must be applied to the atom sites involved in the geometry angle.
|
||||
* Data item specifying the symmetry operation codes applied to the atom
|
||||
* sites involved in a specific geometric configuration.
|
||||
*
|
||||
* The symmetry code of each atom site as the symmetry-equivalent position
|
||||
* number 'n' and the cell translation number 'pqr'. These numbers are
|
||||
@@ -315,8 +316,9 @@ export const CifCore_Schema = {
|
||||
* The character string n_pqr is composed as follows:
|
||||
*
|
||||
* n refers to the symmetry operation that is applied to the
|
||||
* coordinates stored in _atom_site.fract_xyz. It must match a
|
||||
* number given in _symmetry_equiv.pos_site_id.
|
||||
* coordinates stored in _atom_site.fract_xyz. It must match
|
||||
* a number given in _space_group_symop.id (or one of its
|
||||
* aliases, such as _symmetry_equiv_pos_site_id).
|
||||
*
|
||||
* p, q and r refer to the translations that are subsequently
|
||||
* applied to the symmetry transformed coordinates to generate
|
||||
@@ -355,7 +357,7 @@ export const CifCore_Schema = {
|
||||
* of the Internet concepts of Uniform Resource Name and
|
||||
* Universal Resource Locator managed according to the
|
||||
* specifications of the International DOI Foundation
|
||||
* (see http://www.doi.org).
|
||||
* (see https://www.doi.org/).
|
||||
*/
|
||||
block_doi: str,
|
||||
},
|
||||
@@ -414,23 +416,25 @@ export const CifCore_Schema = {
|
||||
*/
|
||||
calc_flag: str,
|
||||
/**
|
||||
* A code which identifies a cluster of atoms that show long range
|
||||
* positional disorder but are locally ordered. Within each such
|
||||
* cluster of atoms, _atom_site.disorder_group is used to identify
|
||||
* the sites that are simultaneously occupied. This field is only
|
||||
* needed if there is more than one cluster of disordered atoms
|
||||
* showing independent local order.
|
||||
* A code which identifies a cluster of atoms that show long range disorder
|
||||
* but are locally ordered. Within each such cluster of atoms,
|
||||
* _atom_site.disorder_group is used to identify the sites that are
|
||||
* simultaneously occupied. This field is only needed if there is more than
|
||||
* one cluster of disordered atoms showing independent local order.
|
||||
*/
|
||||
disorder_assembly: str,
|
||||
/**
|
||||
* A code that identifies a group of positionally disordered atom
|
||||
* sites that are locally simultaneously occupied. Atoms that are
|
||||
* positionally disordered over two or more sites (e.g. the H
|
||||
* atoms of a methyl group that exists in two orientations) can
|
||||
* be assigned to two or more groups. Sites belonging to the same
|
||||
* group are simultaneously occupied, but those belonging to
|
||||
* different groups are not. A minus prefix (e.g. "-1") is used to
|
||||
* indicate sites disordered about a special position.
|
||||
* A code that identifies a group of disordered atom sites that are locally
|
||||
* simultaneously occupied. Atoms that are positionally disordered over two or
|
||||
* more sites (e.g. the H atoms of a methyl group that exists in two
|
||||
* orientations) should be assigned to two or more groups. Similarly, atoms
|
||||
* that describe a specific alternative composition of a compositionally
|
||||
* disordered site should be assigned to a distinct disorder group (e.g. a site
|
||||
* that is partially occupied by Mg and Mn atoms should be described by
|
||||
* assigning the Mg atom to one group and the Mn atom to another group). Sites
|
||||
* belonging to the same group are simultaneously occupied, but those belonging
|
||||
* to different groups are not. A minus prefix (e.g. "-1") is used to indicate
|
||||
* sites disordered about a special position.
|
||||
*/
|
||||
disorder_group: str,
|
||||
/**
|
||||
@@ -491,6 +495,10 @@ export const CifCore_Schema = {
|
||||
* Vol. A (2002). It is equal to the multiplicity of the general
|
||||
* position divided by the order of the site symmetry given in
|
||||
* _atom_site.site_symmetry_order.
|
||||
*
|
||||
* The _atom_site_symmetry_multiplicity form of this data name is
|
||||
* deprecated because of historical inconsistencies in practice among
|
||||
* structure refinement software packages and should not be used.
|
||||
*/
|
||||
site_symmetry_multiplicity: int,
|
||||
/**
|
||||
@@ -502,8 +510,8 @@ export const CifCore_Schema = {
|
||||
type_symbol: str,
|
||||
/**
|
||||
* Isotropic atomic displacement parameter, or equivalent isotropic
|
||||
* atomic displacement parameter, U(equiv), in angstroms squared,
|
||||
* calculated from anisotropic atomic displacement parameters.
|
||||
* atomic displacement parameter, U(equiv), in angstroms squared,
|
||||
* calculated from anisotropic atomic displacement parameters.
|
||||
*
|
||||
* U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~.a~j~)]
|
||||
*
|
||||
@@ -514,8 +522,8 @@ export const CifCore_Schema = {
|
||||
u_iso_or_equiv: float,
|
||||
},
|
||||
/**
|
||||
* The CATEGORY of data items used to describe the anisotropic
|
||||
* thermal parameters of the atomic sites in a crystal structure.
|
||||
* The CATEGORY of data items used to describe the anisotropic atomic
|
||||
* displacement parameters of the atomic sites in a crystal structure.
|
||||
*/
|
||||
atom_site_aniso: {
|
||||
/**
|
||||
@@ -526,94 +534,136 @@ export const CifCore_Schema = {
|
||||
*/
|
||||
label: str,
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u_11: float,
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u: Matrix(3, 3),
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u_12: float,
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u_13: float,
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u_22: float,
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u_23: float,
|
||||
/**
|
||||
* These are the standard anisotropic atomic displacement
|
||||
* components in angstroms squared which appear in the
|
||||
* structure factor term:
|
||||
* These are the standard anisotropic atomic displacement components, in
|
||||
* angstroms squared, which appear in the structure factor term:
|
||||
*
|
||||
* T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
* T = exp{ -2π^2^ sum~i~ [ sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] }
|
||||
*
|
||||
* h = the Miller indices
|
||||
* a* = the reciprocal-space cell lengths
|
||||
*
|
||||
* The unique elements of the real symmetric matrix are entered by row.
|
||||
*
|
||||
* The IUCr Commission on Nomenclature recommends the use of U for reporting
|
||||
* atomic displacement parameters .
|
||||
*
|
||||
* Note that U^ij^ = β^ij^/(2 π^2^ a*~i~ a*~j~) = B^ij^/(8 π^2^) [1].
|
||||
*
|
||||
* [1] Trueblood, K. N. et al. (1996). Acta Crystallogr. A52(5), 770-781.
|
||||
*/
|
||||
u_33: float,
|
||||
},
|
||||
@@ -625,7 +675,7 @@ export const CifCore_Schema = {
|
||||
/**
|
||||
* A description of the atom(s) designated by this atom type. In
|
||||
* most cases this will be the element name and oxidation state of
|
||||
* a single atom species. For disordered or nonstoichiometric
|
||||
* a single atom species. For disordered or nonstoichiometric
|
||||
* structures it will describe a combination of atom species.
|
||||
*/
|
||||
description: str,
|
||||
|
||||
@@ -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.365, IHM 1.18, MA 1.4.4.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.379, IHM 1.23, MA 1.4.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -3710,7 +3710,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The name of the database containing the dataset entry.
|
||||
*/
|
||||
db_name: Aliased<'PDB' | 'PDB-Dev' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'ProXL' | 'Other'>(str),
|
||||
db_name: Aliased<'PDB' | 'PDB-Dev' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'ProXL' | 'jPOSTrepo' | 'iProX' | 'AlphaFoldDB' | 'Other'>(str),
|
||||
/**
|
||||
* The accession code for the database entry.
|
||||
*/
|
||||
@@ -4037,7 +4037,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The type of crosslinker used.
|
||||
*/
|
||||
linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'CYS' | 'SDA' | 'DSA' | 'BrdU' | 'LCSDA' | 'CDI' | 'ADH' | 'Other'>(str),
|
||||
linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'CYS' | 'SDA' | 'DSA' | 'BrdU' | 'LCSDA' | 'CDI' | 'ADH' | 'L-Photo-Leucine' | 'KArGO' | 'BrEtY' | 'DSBU' | 'DSPP' | 'TBDSPP' | 'Other'>(str),
|
||||
/**
|
||||
* Identifier to the crosslinking dataset.
|
||||
* This data item is a pointer to the _ihm_dataset_list.id in the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
@@ -29,6 +29,7 @@ export class MolEncoder extends LigandEncoder {
|
||||
// happens for the unknown ligands (UNL)
|
||||
if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
|
||||
|
||||
let atomCount = 0;
|
||||
let bondCount = 0;
|
||||
let chiral = false;
|
||||
|
||||
@@ -55,6 +56,7 @@ export class MolEncoder extends LigandEncoder {
|
||||
StringBuilder.writeSafe(ctab, ' 0');
|
||||
StringBuilder.writeIntegerPadLeft(ctab, this.mapCharge(charge), 3);
|
||||
StringBuilder.writeSafe(ctab, ' 0 0 0 0 0 0 0 0 0 0\n');
|
||||
atomCount++;
|
||||
if (stereo_config !== 'n') chiral = true;
|
||||
|
||||
// no data for metal ions
|
||||
@@ -76,7 +78,7 @@ export class MolEncoder extends LigandEncoder {
|
||||
});
|
||||
|
||||
// write counts line
|
||||
StringBuilder.writeIntegerPadLeft(this.builder, atoms.size, 3);
|
||||
StringBuilder.writeIntegerPadLeft(this.builder, atomCount, 3);
|
||||
StringBuilder.writeIntegerPadLeft(this.builder, bondCount, 3);
|
||||
StringBuilder.writeSafe(this.builder, ` 0 0 ${chiral ? 1 : 0} 0 0 0 0 0 0\n`);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
@@ -33,6 +33,7 @@ export class Mol2Encoder extends LigandEncoder {
|
||||
const bondMap = this.componentBondData.entries.get(name)!;
|
||||
// happens for the unknown ligands (UNL)
|
||||
if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
|
||||
let atomCount = 0;
|
||||
let bondCount = 0;
|
||||
|
||||
const atoms = this.getAtoms(instance, source);
|
||||
@@ -67,10 +68,11 @@ export class Mol2Encoder extends LigandEncoder {
|
||||
|
||||
const sybyl = bondMap?.map ? this.mapToSybyl(label_atom_id1, type_symbol1, bondMap) : type_symbol1;
|
||||
StringBuilder.writeSafe(a, `${i1 + 1} ${label_atom_id1} ${atom1.Cartn_x.toFixed(3)} ${atom1.Cartn_y.toFixed(3)} ${atom1.Cartn_z.toFixed(3)} ${sybyl} 1 ${name} 0.000\n`);
|
||||
atomCount++;
|
||||
});
|
||||
|
||||
// could write something like 'SMALL\nNO_CHARGES', for now let's write **** indicating non-optional, yet missing, string values
|
||||
StringBuilder.writeSafe(this.out, `@<TRIPOS>MOLECULE\n${name}\n${atoms.size} ${bondCount} 1\n****\n****\n\n`);
|
||||
StringBuilder.writeSafe(this.out, `@<TRIPOS>MOLECULE\n${name}\n${atomCount} ${bondCount} 1\n****\n****\n\n`);
|
||||
StringBuilder.writeSafe(this.out, StringBuilder.getString(a));
|
||||
StringBuilder.writeSafe(this.out, StringBuilder.getString(b));
|
||||
StringBuilder.writeSafe(this.out, `@<TRIPOS>SUBSTRUCTURE\n1 ${name} 1\n`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 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>
|
||||
@@ -49,7 +49,7 @@ export interface IntAdjacencyGraph<VertexIndex extends number, EdgeProps extends
|
||||
export namespace IntAdjacencyGraph {
|
||||
export type EdgePropsBase = { [name: string]: ArrayLike<any> }
|
||||
|
||||
export function areEqual<I extends number, P extends IntAdjacencyGraph.EdgePropsBase>(a: IntAdjacencyGraph<I, P>, b: IntAdjacencyGraph<I, P>) {
|
||||
export function areEqual<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(a: IntAdjacencyGraph<VertexIndex, EdgeProps>, b: IntAdjacencyGraph<VertexIndex, EdgeProps>) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a.vertexCount !== b.vertexCount || a.edgeCount !== b.edgeCount) return false;
|
||||
@@ -149,12 +149,11 @@ export namespace IntAdjacencyGraph {
|
||||
const a = this.xs[this.current], b = this.ys[this.current];
|
||||
|
||||
const oa = this.offsets[a] + this.bucketFill[a];
|
||||
const ob = this.offsets[b] + this.bucketFill[b];
|
||||
|
||||
this.a[oa] = a;
|
||||
this.b[oa] = b;
|
||||
this.bucketFill[a]++;
|
||||
|
||||
const ob = this.offsets[b] + this.bucketFill[b];
|
||||
this.a[ob] = b;
|
||||
this.b[ob] = a;
|
||||
this.bucketFill[b]++;
|
||||
@@ -176,6 +175,13 @@ export namespace IntAdjacencyGraph {
|
||||
prop[this.curB] = value;
|
||||
}
|
||||
|
||||
assignDirectedProperty<T>(propA: { [i: number]: T }, valueA: T, propB: { [i: number]: T }, valueB: T) {
|
||||
propA[this.curA] = valueA;
|
||||
propA[this.curB] = valueB;
|
||||
propB[this.curB] = valueA;
|
||||
propB[this.curA] = valueB;
|
||||
}
|
||||
|
||||
constructor(public vertexCount: number, public xs: ArrayLike<VertexIndex>, public ys: ArrayLike<VertexIndex>) {
|
||||
this.edgeCount = xs.length;
|
||||
this.offsets = new Int32Array(this.vertexCount + 1);
|
||||
@@ -206,11 +212,11 @@ export namespace IntAdjacencyGraph {
|
||||
edgeCount: number;
|
||||
/** the size of the A and B arrays */
|
||||
slotCount: number;
|
||||
a: Int32Array;
|
||||
b: Int32Array;
|
||||
a: AssignableArrayLike<VertexIndex>;
|
||||
b: AssignableArrayLike<VertexIndex>;
|
||||
|
||||
createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(edgeProps: EdgeProps) {
|
||||
return create(this.offsets, this.a, this.b, this.edgeCount, edgeProps);
|
||||
createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase, Props>(edgeProps: EdgeProps, props?: Props) {
|
||||
return create<VertexIndex, EdgeProps, Props>(this.offsets, this.a, this.b, this.edgeCount, edgeProps, props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,8 +267,8 @@ export namespace IntAdjacencyGraph {
|
||||
}
|
||||
this.offsets[this.vertexCount] = offset;
|
||||
this.slotCount = offset;
|
||||
this.a = new Int32Array(offset);
|
||||
this.b = new Int32Array(offset);
|
||||
this.a = new Int32Array(offset) as unknown as AssignableArrayLike<VertexIndex>;
|
||||
this.b = new Int32Array(offset) as unknown as AssignableArrayLike<VertexIndex>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,13 +301,13 @@ export namespace IntAdjacencyGraph {
|
||||
}
|
||||
}
|
||||
|
||||
export function fromVertexPairs<V extends number>(vertexCount: number, xs: V[], ys: V[]) {
|
||||
export function fromVertexPairs<VertexIndex extends number>(vertexCount: number, xs: VertexIndex[], ys: VertexIndex[]) {
|
||||
const graphBuilder = new IntAdjacencyGraph.EdgeBuilder(vertexCount, xs, ys);
|
||||
graphBuilder.addAllEdges();
|
||||
return graphBuilder.createGraph({});
|
||||
}
|
||||
|
||||
export function induceByVertices<V extends number, P extends IntAdjacencyGraph.EdgePropsBase>(graph: IntAdjacencyGraph<V, P>, vertexIndices: ArrayLike<number>): IntAdjacencyGraph<V, P> {
|
||||
export function induceByVertices<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase, Props>(graph: IntAdjacencyGraph<VertexIndex, EdgeProps>, vertexIndices: ArrayLike<number>, props?: Props): IntAdjacencyGraph<VertexIndex, EdgeProps> {
|
||||
const { b, offset, vertexCount, edgeProps } = graph;
|
||||
const vertexMap = new Int32Array(vertexCount);
|
||||
for (let i = 0, _i = vertexIndices.length; i < _i; i++) vertexMap[vertexIndices[i]] = i + 1;
|
||||
@@ -316,8 +322,8 @@ export namespace IntAdjacencyGraph {
|
||||
|
||||
const newOffsets = new Int32Array(vertexIndices.length + 1);
|
||||
const edgeIndices = new Int32Array(2 * newEdgeCount);
|
||||
const newA = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<V>;
|
||||
const newB = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<V>;
|
||||
const newA = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<VertexIndex>;
|
||||
const newB = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<VertexIndex>;
|
||||
let eo = 0, vo = 0;
|
||||
for (let i = 0; i < vertexCount; i++) {
|
||||
if (vertexMap[i] === 0) continue;
|
||||
@@ -326,20 +332,20 @@ export namespace IntAdjacencyGraph {
|
||||
const bb = vertexMap[b[j]];
|
||||
if (bb === 0) continue;
|
||||
|
||||
newA[eo] = aa as V;
|
||||
newB[eo] = bb - 1 as V;
|
||||
newA[eo] = aa as VertexIndex;
|
||||
newB[eo] = bb - 1 as VertexIndex;
|
||||
edgeIndices[eo] = j;
|
||||
eo++;
|
||||
}
|
||||
newOffsets[++vo] = eo;
|
||||
}
|
||||
|
||||
const newEdgeProps = {} as P;
|
||||
for (const key of Object.keys(edgeProps) as (keyof P)[]) {
|
||||
newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices) as P[keyof P];
|
||||
const newEdgeProps = {} as EdgeProps;
|
||||
for (const key of Object.keys(edgeProps) as (keyof EdgeProps)[]) {
|
||||
newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices) as EdgeProps[keyof EdgeProps];
|
||||
}
|
||||
|
||||
return create(newOffsets, newA, newB, newEdgeCount, newEdgeProps);
|
||||
return create<VertexIndex, EdgeProps, Props>(newOffsets, newA, newB, newEdgeCount, newEdgeProps, props);
|
||||
}
|
||||
|
||||
export function connectedComponents(graph: IntAdjacencyGraph<any, any>): { componentCount: number, componentIndex: Int32Array } {
|
||||
|
||||
167
src/mol-math/linear-algebra/3d/euler.ts
Normal file
167
src/mol-math/linear-algebra/3d/euler.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* This code has been modified from https://github.com/mrdoob/three.js/,
|
||||
* copyright (c) 2010-2023 three.js authors. MIT License
|
||||
*/
|
||||
|
||||
import { Mat4 } from './mat4';
|
||||
import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
|
||||
import { Quat } from './quat';
|
||||
import { Vec3 } from './vec3';
|
||||
import { clamp } from '../../interpolate';
|
||||
|
||||
interface Euler extends Array<number> { [d: number]: number, '@type': 'euler', length: 3 }
|
||||
|
||||
function Euler() {
|
||||
return Euler.zero();
|
||||
}
|
||||
|
||||
namespace Euler {
|
||||
export type Order = 'XYZ' | 'YXZ' | 'ZXY' | 'ZYX' | 'YZX' | 'XZY'
|
||||
|
||||
export function zero(): Euler {
|
||||
// force double backing array by 0.1.
|
||||
const ret = [0.1, 0, 0];
|
||||
ret[0] = 0.0;
|
||||
return ret as any;
|
||||
}
|
||||
|
||||
export function create(x: number, y: number, z: number): Euler {
|
||||
const out = zero();
|
||||
out[0] = x;
|
||||
out[1] = y;
|
||||
out[2] = z;
|
||||
return out;
|
||||
}
|
||||
|
||||
export function set(out: Euler, x: number, y: number, z: number) {
|
||||
out[0] = x;
|
||||
out[0] = y;
|
||||
out[0] = z;
|
||||
return out;
|
||||
}
|
||||
|
||||
export function clone(a: Euler): Euler {
|
||||
const out = zero();
|
||||
out[0] = a[0];
|
||||
out[1] = a[1];
|
||||
out[2] = a[2];
|
||||
return out;
|
||||
}
|
||||
|
||||
export function copy(out: Euler, a: Euler) {
|
||||
out[0] = a[0];
|
||||
out[1] = a[1];
|
||||
out[2] = a[2];
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
|
||||
*/
|
||||
export function fromMat4(out: Euler, m: Mat4, order: Order): Euler {
|
||||
const m11 = m[0], m12 = m[4], m13 = m[8];
|
||||
const m21 = m[1], m22 = m[5], m23 = m[9];
|
||||
const m31 = m[2], m32 = m[6], m33 = m[10];
|
||||
|
||||
switch (order) {
|
||||
case 'XYZ':
|
||||
out[1] = Math.asin(clamp(m13, -1, 1));
|
||||
if (Math.abs(m13) < 0.9999999) {
|
||||
out[0] = Math.atan2(-m23, m33);
|
||||
out[2] = Math.atan2(-m12, m11);
|
||||
} else {
|
||||
out[0] = Math.atan2(m32, m22);
|
||||
out[2] = 0;
|
||||
}
|
||||
break;
|
||||
case 'YXZ':
|
||||
out[0] = Math.asin(-clamp(m23, -1, 1));
|
||||
if (Math.abs(m23) < 0.9999999) {
|
||||
out[1] = Math.atan2(m13, m33);
|
||||
out[2] = Math.atan2(m21, m22);
|
||||
} else {
|
||||
out[1] = Math.atan2(-m31, m11);
|
||||
out[2] = 0;
|
||||
}
|
||||
break;
|
||||
case 'ZXY':
|
||||
out[0] = Math.asin(clamp(m32, -1, 1));
|
||||
if (Math.abs(m32) < 0.9999999) {
|
||||
out[1] = Math.atan2(-m31, m33);
|
||||
out[2] = Math.atan2(-m12, m22);
|
||||
} else {
|
||||
out[1] = 0;
|
||||
out[2] = Math.atan2(m21, m11);
|
||||
}
|
||||
break;
|
||||
case 'ZYX':
|
||||
out[1] = Math.asin(-clamp(m31, -1, 1));
|
||||
if (Math.abs(m31) < 0.9999999) {
|
||||
out[0] = Math.atan2(m32, m33);
|
||||
out[2] = Math.atan2(m21, m11);
|
||||
} else {
|
||||
out[0] = 0;
|
||||
out[2] = Math.atan2(-m12, m22);
|
||||
}
|
||||
break;
|
||||
case 'YZX':
|
||||
out[2] = Math.asin(clamp(m21, -1, 1));
|
||||
if (Math.abs(m21) < 0.9999999) {
|
||||
out[0] = Math.atan2(-m23, m22);
|
||||
out[1] = Math.atan2(-m31, m11);
|
||||
} else {
|
||||
out[0] = 0;
|
||||
out[1] = Math.atan2(m13, m33);
|
||||
}
|
||||
break;
|
||||
case 'XZY':
|
||||
out[2] = Math.asin(-clamp(m12, -1, 1));
|
||||
if (Math.abs(m12) < 0.9999999) {
|
||||
out[0] = Math.atan2(m32, m22);
|
||||
out[1] = Math.atan2(m13, m11);
|
||||
} else {
|
||||
out[0] = Math.atan2(-m23, m33);
|
||||
out[1] = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(order);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const _mat4 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat4;
|
||||
export function fromQuat(out: Euler, q: Quat, order: Order) {
|
||||
Mat4.fromQuat(_mat4, q);
|
||||
return fromMat4(out, _mat4, order);
|
||||
}
|
||||
|
||||
export function fromVec3(out: Euler, v: Vec3) {
|
||||
return set(out, v[0], v[1], v[2]);
|
||||
}
|
||||
|
||||
export function exactEquals(a: Euler, b: Euler) {
|
||||
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
||||
}
|
||||
|
||||
export function fromArray(e: Euler, array: ArrayLike<number>, offset: number) {
|
||||
e[0] = array[offset + 0];
|
||||
e[1] = array[offset + 1];
|
||||
e[2] = array[offset + 2];
|
||||
return e;
|
||||
}
|
||||
|
||||
export function toArray<T extends NumberArray>(e: Euler, out: T, offset: number) {
|
||||
out[offset + 0] = e[0];
|
||||
out[offset + 1] = e[1];
|
||||
out[offset + 2] = e[2];
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
export { Euler };
|
||||
@@ -407,12 +407,12 @@ namespace Mat3 {
|
||||
return out;
|
||||
}
|
||||
|
||||
const tmpR0 = [0.1, 0.0, 0.0] as Vec3;
|
||||
const tmpR1 = [0.1, 0.0, 0.0] as Vec3;
|
||||
const tmpR2 = [0.1, 0.0, 0.0] as Vec3;
|
||||
const tmpR0xR1 = [0.1, 0.0, 0.0] as Vec3;
|
||||
const tmpR0xR2 = [0.1, 0.0, 0.0] as Vec3;
|
||||
const tmpR1xR2 = [0.1, 0.0, 0.0] as Vec3;
|
||||
const tmpR0 = [0.1, 0.0, 0.0] as unknown as Vec3;
|
||||
const tmpR1 = [0.1, 0.0, 0.0] as unknown as Vec3;
|
||||
const tmpR2 = [0.1, 0.0, 0.0] as unknown as Vec3;
|
||||
const tmpR0xR1 = [0.1, 0.0, 0.0] as unknown as Vec3;
|
||||
const tmpR0xR2 = [0.1, 0.0, 0.0] as unknown as Vec3;
|
||||
const tmpR1xR2 = [0.1, 0.0, 0.0] as unknown as Vec3;
|
||||
/**
|
||||
* Calculates the eigenvector for the given eigenvalue `e` of matrix `a`
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Quat } from './quat';
|
||||
import { degToRad } from '../../misc';
|
||||
import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
import { Mat3 } from './mat3';
|
||||
import { Euler } from './euler';
|
||||
|
||||
interface Mat4 extends Array<number> { [d: number]: number, '@type': 'mat4', length: 16 }
|
||||
interface ReadonlyMat4 extends Array<number> { readonly [d: number]: number, '@type': 'mat4', length: 16 }
|
||||
@@ -717,6 +718,82 @@ namespace Mat4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function compose(out: Mat4, position: Vec3, quaternion: Quat, scale: Vec3) {
|
||||
const [x, y, z, w] = quaternion;
|
||||
const x2 = x + x, y2 = y + y, z2 = z + z;
|
||||
const xx = x * x2, xy = x * y2, xz = x * z2;
|
||||
const yy = y * y2, yz = y * z2, zz = z * z2;
|
||||
const wx = w * x2, wy = w * y2, wz = w * z2;
|
||||
|
||||
const [sx, sy, sz] = scale;
|
||||
|
||||
out[0] = (1 - (yy + zz)) * sx;
|
||||
out[1] = (xy + wz) * sx;
|
||||
out[2] = (xz - wy) * sx;
|
||||
out[3] = 0;
|
||||
|
||||
out[4] = (xy - wz) * sy;
|
||||
out[5] = (1 - (xx + zz)) * sy;
|
||||
out[6] = (yz + wx) * sy;
|
||||
out[7] = 0;
|
||||
|
||||
out[8] = (xz + wy) * sz;
|
||||
out[9] = (yz - wx) * sz;
|
||||
out[10] = (1 - (xx + yy)) * sz;
|
||||
out[11] = 0;
|
||||
|
||||
out[12] = position[0];
|
||||
out[13] = position[1];
|
||||
out[14] = position[2];
|
||||
out[15] = 1;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const _v3 = [0, 0, 0] as unknown as Vec3;
|
||||
const _m4 = zero();
|
||||
export function decompose(m: Mat4, position: Vec3, quaternion: Quat, scale: Vec3) {
|
||||
|
||||
let sx = Vec3.magnitude(Vec3.set(_v3, m[0], m[1], m[2]));
|
||||
const sy = Vec3.magnitude(Vec3.set(_v3, m[4], m[5], m[6]));
|
||||
const sz = Vec3.magnitude(Vec3.set(_v3, m[8], m[9], m[10]));
|
||||
|
||||
// if determine is negative, we need to invert one scale
|
||||
const det = determinant(m);
|
||||
if (det < 0) sx = -sx;
|
||||
|
||||
position[0] = m[12];
|
||||
position[1] = m[13];
|
||||
position[2] = m[14];
|
||||
|
||||
// scale the rotation part
|
||||
copy(_m4, m);
|
||||
|
||||
const invSX = 1 / sx;
|
||||
const invSY = 1 / sy;
|
||||
const invSZ = 1 / sz;
|
||||
|
||||
_m4[0] *= invSX;
|
||||
_m4[1] *= invSX;
|
||||
_m4[2] *= invSX;
|
||||
|
||||
_m4[4] *= invSY;
|
||||
_m4[5] *= invSY;
|
||||
_m4[6] *= invSY;
|
||||
|
||||
_m4[8] *= invSZ;
|
||||
_m4[9] *= invSZ;
|
||||
_m4[10] *= invSZ;
|
||||
|
||||
getRotation(quaternion, _m4);
|
||||
|
||||
scale[0] = sx;
|
||||
scale[1] = sy;
|
||||
scale[2] = sz;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
export function makeTable(m: Mat4) {
|
||||
let ret = '';
|
||||
for (let i = 0; i < 4; i++) {
|
||||
@@ -851,6 +928,94 @@ namespace Mat4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function fromEuler(out: Mat4, euler: Euler, order: Euler.Order) {
|
||||
const x = euler[0], y = euler[1], z = euler[2];
|
||||
const a = Math.cos(x), b = Math.sin(x);
|
||||
const c = Math.cos(y), d = Math.sin(y);
|
||||
const e = Math.cos(z), f = Math.sin(z);
|
||||
|
||||
if (order === 'XYZ') {
|
||||
const ae = a * e, af = a * f, be = b * e, bf = b * f;
|
||||
out[0] = c * e;
|
||||
out[4] = - c * f;
|
||||
out[8] = d;
|
||||
out[1] = af + be * d;
|
||||
out[5] = ae - bf * d;
|
||||
out[9] = - b * c;
|
||||
out[2] = bf - ae * d;
|
||||
out[6] = be + af * d;
|
||||
out[10] = a * c;
|
||||
} else if (order === 'YXZ') {
|
||||
const ce = c * e, cf = c * f, de = d * e, df = d * f;
|
||||
out[0] = ce + df * b;
|
||||
out[4] = de * b - cf;
|
||||
out[8] = a * d;
|
||||
out[1] = a * f;
|
||||
out[5] = a * e;
|
||||
out[9] = - b;
|
||||
out[2] = cf * b - de;
|
||||
out[6] = df + ce * b;
|
||||
out[10] = a * c;
|
||||
} else if (order === 'ZXY') {
|
||||
const ce = c * e, cf = c * f, de = d * e, df = d * f;
|
||||
out[0] = ce - df * b;
|
||||
out[4] = - a * f;
|
||||
out[8] = de + cf * b;
|
||||
out[1] = cf + de * b;
|
||||
out[5] = a * e;
|
||||
out[9] = df - ce * b;
|
||||
out[2] = - a * d;
|
||||
out[6] = b;
|
||||
out[10] = a * c;
|
||||
} else if (order === 'ZYX') {
|
||||
const ae = a * e, af = a * f, be = b * e, bf = b * f;
|
||||
out[0] = c * e;
|
||||
out[4] = be * d - af;
|
||||
out[8] = ae * d + bf;
|
||||
out[1] = c * f;
|
||||
out[5] = bf * d + ae;
|
||||
out[9] = af * d - be;
|
||||
out[2] = - d;
|
||||
out[6] = b * c;
|
||||
out[10] = a * c;
|
||||
} else if (order === 'YZX') {
|
||||
const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
|
||||
out[0] = c * e;
|
||||
out[4] = bd - ac * f;
|
||||
out[8] = bc * f + ad;
|
||||
out[1] = f;
|
||||
out[5] = a * e;
|
||||
out[9] = - b * e;
|
||||
out[2] = - d * e;
|
||||
out[6] = ad * f + bc;
|
||||
out[10] = ac - bd * f;
|
||||
} else if (order === 'XZY') {
|
||||
const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
|
||||
out[0] = c * e;
|
||||
out[4] = - f;
|
||||
out[8] = d * e;
|
||||
out[1] = ac * f + bd;
|
||||
out[5] = a * e;
|
||||
out[9] = ad * f - bc;
|
||||
out[2] = bc * f - ad;
|
||||
out[6] = b * e;
|
||||
out[10] = bd * f + ac;
|
||||
}
|
||||
|
||||
// bottom row
|
||||
out[3] = 0;
|
||||
out[7] = 0;
|
||||
out[11] = 0;
|
||||
|
||||
// last column
|
||||
out[12] = 0;
|
||||
out[13] = 0;
|
||||
out[14] = 0;
|
||||
out[15] = 1;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a perspective projection (frustum) matrix with the given bounds
|
||||
*/
|
||||
@@ -1068,9 +1233,9 @@ namespace Mat4 {
|
||||
return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));
|
||||
}
|
||||
|
||||
const xAxis = [1, 0, 0] as Vec3;
|
||||
const yAxis = [0, 1, 0] as Vec3;
|
||||
const zAxis = [0, 0, 1] as Vec3;
|
||||
const xAxis = [1, 0, 0] as unknown as Vec3;
|
||||
const yAxis = [0, 1, 0] as unknown as Vec3;
|
||||
const zAxis = [0, 0, 1] as unknown as Vec3;
|
||||
|
||||
/** Rotation matrix for 90deg around x-axis */
|
||||
export const rotX90: ReadonlyMat4 = fromRotation(zero(), degToRad(90), xAxis);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2023 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>
|
||||
@@ -25,7 +25,8 @@
|
||||
import { Mat3 } from './mat3';
|
||||
import { Vec3 } from './vec3';
|
||||
import { EPSILON } from './common';
|
||||
import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
|
||||
import { Euler } from './euler';
|
||||
|
||||
interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
|
||||
interface ReadonlyQuat extends Array<number> { readonly [d: number]: number, '@type': 'quat', length: 4 }
|
||||
@@ -238,6 +239,10 @@ namespace Quat {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function dot(a: Quat, b: Quat) {
|
||||
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a quaternion from the given 3x3 rotation matrix.
|
||||
*
|
||||
@@ -277,7 +282,64 @@ namespace Quat {
|
||||
return out;
|
||||
}
|
||||
|
||||
const fromUnitVec3Temp = [0, 0, 0] as Vec3;
|
||||
export function fromEuler(out: Quat, euler: Euler, order: Euler.Order) {
|
||||
const [x, y, z] = euler;
|
||||
|
||||
// http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
|
||||
|
||||
const c1 = Math.cos(x / 2);
|
||||
const c2 = Math.cos(y / 2);
|
||||
const c3 = Math.cos(z / 2);
|
||||
|
||||
const s1 = Math.sin(x / 2);
|
||||
const s2 = Math.sin(y / 2);
|
||||
const s3 = Math.sin(z / 2);
|
||||
|
||||
switch (order) {
|
||||
case 'XYZ':
|
||||
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
|
||||
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
|
||||
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
|
||||
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
|
||||
break;
|
||||
case 'YXZ':
|
||||
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
|
||||
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
|
||||
out[2] = c1 * c2 * s3 - s1 * s2 * c3;
|
||||
out[3] = c1 * c2 * c3 + s1 * s2 * s3;
|
||||
break;
|
||||
case 'ZXY':
|
||||
out[0] = s1 * c2 * c3 - c1 * s2 * s3;
|
||||
out[1] = c1 * s2 * c3 + s1 * c2 * s3;
|
||||
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
|
||||
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
|
||||
break;
|
||||
case 'ZYX':
|
||||
out[0] = s1 * c2 * c3 - c1 * s2 * s3;
|
||||
out[1] = c1 * s2 * c3 + s1 * c2 * s3;
|
||||
out[2] = c1 * c2 * s3 - s1 * s2 * c3;
|
||||
out[3] = c1 * c2 * c3 + s1 * s2 * s3;
|
||||
break;
|
||||
case 'YZX':
|
||||
out[0] = s1 * c2 * c3 + c1 * s2 * s3;
|
||||
out[1] = c1 * s2 * c3 + s1 * c2 * s3;
|
||||
out[2] = c1 * c2 * s3 - s1 * s2 * c3;
|
||||
out[3] = c1 * c2 * c3 - s1 * s2 * s3;
|
||||
break;
|
||||
case 'XZY':
|
||||
out[0] = s1 * c2 * c3 - c1 * s2 * s3;
|
||||
out[1] = c1 * s2 * c3 - s1 * c2 * s3;
|
||||
out[2] = c1 * c2 * s3 + s1 * s2 * c3;
|
||||
out[3] = c1 * c2 * c3 + s1 * s2 * s3;
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(order);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const fromUnitVec3Temp = [0, 0, 0] as unknown as Vec3;
|
||||
/** Quaternion from two normalized unit vectors. */
|
||||
export function fromUnitVec3(out: Quat, a: Vec3, b: Vec3) {
|
||||
// assumes a and b are normalized
|
||||
@@ -395,9 +457,9 @@ namespace Quat {
|
||||
*
|
||||
* Both vectors are assumed to be unit length.
|
||||
*/
|
||||
const rotTmpVec3 = [0, 0, 0] as Vec3;
|
||||
const rotTmpVec3UnitX = [1, 0, 0] as Vec3;
|
||||
const rotTmpVec3UnitY = [0, 1, 0] as Vec3;
|
||||
const rotTmpVec3 = [0, 0, 0] as unknown as Vec3;
|
||||
const rotTmpVec3UnitX = [1, 0, 0] as unknown as Vec3;
|
||||
const rotTmpVec3UnitY = [0, 1, 0] as unknown as Vec3;
|
||||
export function rotationTo(out: Quat, a: Vec3, b: Vec3) {
|
||||
const dot = Vec3.dot(a, b);
|
||||
if (dot < -0.999999) {
|
||||
@@ -440,7 +502,7 @@ namespace Quat {
|
||||
* axes. Each axis is a vec3 and is expected to be unit length and
|
||||
* perpendicular to all other specified axes.
|
||||
*/
|
||||
const axesTmpMat = [0, 0, 0, 0, 0, 0, 0, 0, 0] as Mat3;
|
||||
const axesTmpMat = [0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat3;
|
||||
export function setAxes(out: Quat, view: Vec3, right: Vec3, up: Vec3) {
|
||||
axesTmpMat[0] = right[0];
|
||||
axesTmpMat[3] = right[1];
|
||||
|
||||
35
src/mol-math/linear-algebra/_spec/euler.spec.ts
Normal file
35
src/mol-math/linear-algebra/_spec/euler.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../3d/mat4';
|
||||
import { Euler } from '../3d/euler';
|
||||
import { Quat } from '../3d/quat';
|
||||
|
||||
const t = [
|
||||
[Euler.create(0, 0, 0), 'XYZ'],
|
||||
[Euler.create(1, 0, 0), 'XYZ'],
|
||||
[Euler.create(0, 1, 0), 'ZYX'],
|
||||
] as const;
|
||||
|
||||
describe('Euler', () => {
|
||||
it('fromMat4', () => {
|
||||
for (const [e, o] of t) {
|
||||
const m = Mat4.fromEuler(Mat4(), e, o);
|
||||
const e2 = Euler.fromMat4(Euler(), m, o);
|
||||
const m2 = Mat4.fromEuler(Mat4(), e2, o);
|
||||
expect(Mat4.areEqual(m, m2, 0.0001)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('fromQuat', () => {
|
||||
for (const [e, o] of t) {
|
||||
const q = Quat.fromEuler(Quat(), e, o);
|
||||
const e2 = Euler.fromQuat(Euler(), q, o);
|
||||
const q2 = Quat.fromEuler(Quat(), e2, o);
|
||||
expect(Quat.equals(q, q2)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,10 +7,10 @@
|
||||
import { Vec3 } from '../3d/vec3';
|
||||
|
||||
describe('vec3', () => {
|
||||
const vec1 = [1, 2, 3] as Vec3;
|
||||
const vec2 = [2, 3, 1] as Vec3;
|
||||
const orthVec1 = [0, 1, 0] as Vec3;
|
||||
const orthVec2 = [1, 0, 0] as Vec3;
|
||||
const vec1 = Vec3.create(1, 2, 3);
|
||||
const vec2 = Vec3.create(2, 3, 1);
|
||||
const orthVec1 = Vec3.create(0, 1, 0);
|
||||
const orthVec2 = Vec3.create(1, 0, 0);
|
||||
|
||||
it('angle calculation', () => {
|
||||
expect(Vec3.angle(vec1, vec1) * 360 / (2 * Math.PI)).toBe(0.0);
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2023 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>
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
|
||||
import { Model } from '../../mol-model/structure/model/model';
|
||||
import { Task } from '../../mol-task';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CifFrame, CIF } from '../../mol-io/reader/cif';
|
||||
import { CifFrame, CIF, CifFile } from '../../mol-io/reader/cif';
|
||||
import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { createModels } from './basic/parser';
|
||||
import { ModelSymmetry } from './property/symmetry';
|
||||
import { ModelSecondaryStructure } from './property/secondary-structure';
|
||||
import { Table } from '../../mol-data/db';
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { AtomSiteAnisotrop } from './property/anisotropic';
|
||||
import { ComponentBond } from './property/bonds/chem_comp';
|
||||
import { StructConn } from './property/bonds/struct_conn';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { ArrayTrajectory, Trajectory } from '../../mol-model/structure';
|
||||
import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
|
||||
import { createBasic } from './basic/schema';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { CCD_Database } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
|
||||
function modelSymmetryFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -83,6 +88,7 @@ namespace MmcifFormat {
|
||||
export type Data = {
|
||||
db: mmCIF_Database,
|
||||
frame: CifFrame,
|
||||
file?: CifFile,
|
||||
/**
|
||||
* Original source format. Some formats, including PDB, are converted
|
||||
* to mmCIF before further processing.
|
||||
@@ -93,14 +99,146 @@ namespace MmcifFormat {
|
||||
return x?.kind === 'mmCIF';
|
||||
}
|
||||
|
||||
export function fromFrame(frame: CifFrame, db?: mmCIF_Database, source?: ModelFormat): MmcifFormat {
|
||||
export function fromFrame(frame: CifFrame, db?: mmCIF_Database, source?: ModelFormat, file?: CifFile): MmcifFormat {
|
||||
if (!db) db = CIF.schema.mmCIF(frame);
|
||||
return { kind: 'mmCIF', name: db._name, data: { db, frame, source } };
|
||||
return { kind: 'mmCIF', name: db._name, data: { db, file, frame, source } };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromMmCIF(frame: CifFrame): Task<Trajectory> {
|
||||
const format = MmcifFormat.fromFrame(frame);
|
||||
export function trajectoryFromMmCIF(frame: CifFrame, file?: CifFile): Task<Trajectory> {
|
||||
const format = MmcifFormat.fromFrame(frame, undefined, undefined, file);
|
||||
const basic = createBasic(format.data.db, true);
|
||||
return Task.create('Create mmCIF Model', ctx => createModels(basic, format, ctx));
|
||||
}
|
||||
|
||||
export { CCDFormat };
|
||||
|
||||
type CCDFormat = ModelFormat<CCDFormat.Data>
|
||||
|
||||
namespace CCDFormat {
|
||||
export type Data = {
|
||||
db: CCD_Database,
|
||||
frame: CifFrame
|
||||
}
|
||||
|
||||
const CoordinateTypeProp = '__CcdCoordinateType__';
|
||||
export type CoordinateType = 'ideal' | 'model'
|
||||
export const CoordinateType = {
|
||||
get(model: Model): CoordinateType | undefined {
|
||||
return model._staticPropertyData[CoordinateTypeProp];
|
||||
},
|
||||
set(model: Model, type: CoordinateType) {
|
||||
return model._staticPropertyData[CoordinateTypeProp] = type;
|
||||
}
|
||||
};
|
||||
|
||||
export function is(x?: ModelFormat): x is CCDFormat {
|
||||
return x?.kind === 'CCD';
|
||||
}
|
||||
|
||||
export function fromFrame(frame: CifFrame, db?: CCD_Database): CCDFormat {
|
||||
if (!db) db = CIF.schema.CCD(frame);
|
||||
return { kind: 'CCD', name: db._name, data: { db, frame } };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromCCD(frame: CifFrame): Task<Trajectory> {
|
||||
const format = CCDFormat.fromFrame(frame);
|
||||
return Task.create('Create CCD Models', ctx => createCcdModels(format.data.db, CCDFormat.fromFrame(frame), ctx));
|
||||
}
|
||||
|
||||
async function createCcdModels(data: CCD_Database, format: CCDFormat, ctx: RuntimeContext) {
|
||||
const ideal = await createCcdModel(data, format, { coordinateType: 'ideal', cartn_x: 'pdbx_model_Cartn_x_ideal', cartn_y: 'pdbx_model_Cartn_y_ideal', cartn_z: 'pdbx_model_Cartn_z_ideal' }, ctx);
|
||||
const model = await createCcdModel(data, format, { coordinateType: 'model', cartn_x: 'model_Cartn_x', cartn_y: 'model_Cartn_y', cartn_z: 'model_Cartn_z' }, ctx);
|
||||
|
||||
const models = [];
|
||||
if (ideal) models.push(ideal);
|
||||
if (model) models.push(model);
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(models[i], { index: i, size: models.length });
|
||||
}
|
||||
|
||||
return new ArrayTrajectory(models);
|
||||
}
|
||||
|
||||
type CCDProps = { coordinateType: CCDFormat.CoordinateType, cartn_x: 'model_Cartn_x' | 'pdbx_model_Cartn_x_ideal', cartn_y: 'model_Cartn_y' | 'pdbx_model_Cartn_y_ideal', cartn_z: 'model_Cartn_z' | 'pdbx_model_Cartn_z_ideal' };
|
||||
async function createCcdModel(data: CCD_Database, format: CCDFormat, props: CCDProps, ctx: RuntimeContext) {
|
||||
const { chem_comp, chem_comp_atom, chem_comp_bond } = data;
|
||||
const { coordinateType, cartn_x, cartn_y, cartn_z } = props;
|
||||
|
||||
const name = chem_comp.name.value(0);
|
||||
const id = chem_comp.id.value(0);
|
||||
|
||||
const { atom_id, charge, comp_id, pdbx_ordinal, type_symbol } = chem_comp_atom;
|
||||
const atomCount = chem_comp_atom._rowCount;
|
||||
|
||||
const filteredRows: number[] = [];
|
||||
for (let i = 0; i < atomCount; i++) {
|
||||
if (chem_comp_atom[cartn_x].valueKind(i) > 0) continue;
|
||||
filteredRows[filteredRows.length] = i;
|
||||
}
|
||||
const filteredRowCount = filteredRows.length;
|
||||
|
||||
const A = Column.ofConst('A', filteredRowCount, Column.Schema.str);
|
||||
const seq_id = Column.ofConst(1, filteredRowCount, Column.Schema.int);
|
||||
const entity_id = Column.ofConst('1', filteredRowCount, Column.Schema.str);
|
||||
const occupancy = Column.ofConst(1, filteredRowCount, Column.Schema.float);
|
||||
const model_num = Column.ofConst(1, filteredRowCount, Column.Schema.int);
|
||||
|
||||
const filteredAtomId = Column.view(atom_id, filteredRows);
|
||||
const filteredCompId = Column.view(comp_id, filteredRows);
|
||||
const filteredX = Column.view(chem_comp_atom[cartn_x], filteredRows);
|
||||
const filteredY = Column.view(chem_comp_atom[cartn_y], filteredRows);
|
||||
const filteredZ = Column.view(chem_comp_atom[cartn_z], filteredRows);
|
||||
const filteredId = Column.view(pdbx_ordinal, filteredRows);
|
||||
const filteredTypeSymbol = Column.view(type_symbol, filteredRows);
|
||||
const filteredCharge = Column.view(charge, filteredRows);
|
||||
|
||||
const model_atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: A,
|
||||
auth_atom_id: filteredAtomId,
|
||||
auth_comp_id: filteredCompId,
|
||||
auth_seq_id: seq_id,
|
||||
Cartn_x: filteredX,
|
||||
Cartn_y: filteredY,
|
||||
Cartn_z: filteredZ,
|
||||
id: filteredId,
|
||||
|
||||
label_asym_id: A,
|
||||
label_atom_id: filteredAtomId,
|
||||
label_comp_id: filteredCompId,
|
||||
label_seq_id: seq_id,
|
||||
label_entity_id: entity_id,
|
||||
|
||||
occupancy,
|
||||
type_symbol: filteredTypeSymbol,
|
||||
|
||||
pdbx_PDB_model_num: model_num,
|
||||
pdbx_formal_charge: filteredCharge
|
||||
}, filteredRowCount);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
entityBuilder.setNames([[id, `${name} (${coordinateType})`]]);
|
||||
entityBuilder.getEntityId(id, MoleculeType.Unknown, 'A');
|
||||
|
||||
const componentBuilder = new ComponentBuilder(seq_id, type_symbol);
|
||||
componentBuilder.setNames([[id, `${name} (${coordinateType})`]]);
|
||||
componentBuilder.add(id, 0);
|
||||
|
||||
const basicModel = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site: model_atom_site
|
||||
});
|
||||
const models = await createModels(basicModel, format, ctx);
|
||||
|
||||
// all ideal or model coordinates might be absent
|
||||
if (!models.representative) return;
|
||||
|
||||
const first = models.representative;
|
||||
const entries = ComponentBond.getEntriesFromChemCompBond(chem_comp_bond);
|
||||
ComponentBond.Provider.set(first, { data: chem_comp_bond, entries });
|
||||
CCDFormat.CoordinateType.set(first, coordinateType);
|
||||
|
||||
return models.representative;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ function createAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assemb
|
||||
return Assembly.create(id, details, operatorGroupsProvider(generators, matrices));
|
||||
}
|
||||
|
||||
function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups {
|
||||
export function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups {
|
||||
return () => {
|
||||
const groups: OperatorGroup[] = [];
|
||||
|
||||
@@ -71,7 +71,7 @@ function operatorGroupsProvider(generators: Generator[], matrices: Matrices): ()
|
||||
};
|
||||
}
|
||||
|
||||
function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
|
||||
export function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
|
||||
const { id, matrix, vector, _schema } = pdbx_struct_oper_list;
|
||||
const matrices = new Map<string, Mat4>();
|
||||
const t = Vec3();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 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>
|
||||
@@ -34,8 +34,10 @@ function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementInde
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(key, props.key ? props.key[i] : -1);
|
||||
builder.assignProperty(operatorA, props.operatorA ? props.operatorA[i] : -1);
|
||||
builder.assignProperty(operatorB, props.operatorB ? props.operatorB[i] : -1);
|
||||
builder.assignDirectedProperty(
|
||||
operatorA, props.operatorA ? props.operatorA[i] : -1,
|
||||
operatorB, props.operatorB ? props.operatorB[i] : -1
|
||||
);
|
||||
builder.assignProperty(order, props.order ? props.order[i] : 1);
|
||||
builder.assignProperty(distance, props.distance ? props.distance[i] : -1);
|
||||
builder.assignProperty(flag, props.flag ? props.flag[i] : BondType.Flag.Covalent);
|
||||
|
||||
@@ -46,8 +46,8 @@ export function guessElementSymbolTokens(tokens: Tokens, str: string, start: num
|
||||
TokenBuilder.add(tokens, s, s); // no reasonable guess, add empty token
|
||||
}
|
||||
|
||||
const TwoCharElementNames = new Set(['NA', 'CL', 'FE', 'SI', 'BR', 'AS']);
|
||||
const OneCharElementNames = new Set(['C', 'H', 'N', 'O', 'P', 'S']);
|
||||
const TwoCharElementNames = new Set(['NA', 'CL', 'FE', 'SI', 'BR', 'AS', 'LI']);
|
||||
const OneCharElementNames = new Set(['C', 'H', 'N', 'O', 'P', 'S', 'F', 'B']);
|
||||
|
||||
const reTrimSpacesAndNumbers = /^[\s\d]+|[\s\d]+$/g;
|
||||
export function guessElementSymbolString(atomId: string, compId: string) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021-23 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -52,8 +52,7 @@ namespace SIFTSMapping {
|
||||
const model = loc.unit.model;
|
||||
const data = Provider.get(model).value;
|
||||
if (!data) return '';
|
||||
const eI = loc.unit.elements[loc.element];
|
||||
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
|
||||
const rI = model.atomicHierarchy.residueAtomSegments.index[loc.element];
|
||||
return data.accession[rI];
|
||||
}
|
||||
|
||||
@@ -61,9 +60,9 @@ namespace SIFTSMapping {
|
||||
const model = loc.unit.model;
|
||||
const data = Provider.get(model).value;
|
||||
if (!data) return;
|
||||
const eI = loc.unit.elements[loc.element];
|
||||
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
|
||||
const rI = model.atomicHierarchy.residueAtomSegments.index[loc.element];
|
||||
const dbName = data.dbName[rI];
|
||||
|
||||
if (!dbName) return;
|
||||
return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`;
|
||||
}
|
||||
|
||||
@@ -286,7 +286,12 @@ export const AminoAcidNamesD = new Set([
|
||||
export const AminoAcidNames = SetUtils.unionMany(AminoAcidNamesL, AminoAcidNamesD);
|
||||
|
||||
export const CommonProteinCaps = new Set([
|
||||
'NME', 'ACE'
|
||||
'NME', 'ACE', 'NH2', 'FOR', 'FMT'
|
||||
// not including the following
|
||||
// 'E1H' GFP backbone fragmentation in 2G16
|
||||
// 'HOA' complexes zinc
|
||||
// 'NEH' ubiquitine linker
|
||||
// 'MOH' part of peptidomimetics
|
||||
]);
|
||||
|
||||
export const RnaBaseNames = new Set([
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
* @author molstar/chem-comp-dict/create-ions cli
|
||||
*/
|
||||
|
||||
export const IonNames = new Set(['118', '119', '543', '1AL', '1CU', '2FK', '2HP', '2OF', '3CO', '3MT', '3NI', '3OF', '3P8', '4MO', '4PU', '4TI', '6MO', 'ACT', 'AG', 'AL', 'ALF', 'AM', 'ATH', 'AU', 'AU3', 'AUC', 'AZI', 'BA', 'BCT', 'BEF', 'BF4', 'BO4', 'BR', 'BS3', 'BSY', 'CA', 'CAC', 'CD', 'CD1', 'CD3', 'CD5', 'CE', 'CF', 'CHT', 'CL', 'CO', 'CO3', 'CO5', 'CON', 'CR', 'CS', 'CSB', 'CU', 'CU1', 'CU3', 'CUA', 'CUZ', 'CYN', 'DME', 'DMI', 'DSC', 'DTI', 'DY', 'E4N', 'EDR', 'EMC', 'ER3', 'EU', 'EU3', 'F', 'FE', 'FE2', 'FPO', 'GA', 'GD3', 'GEP', 'HAI', 'HG', 'HGC', 'IN', 'IOD', 'IR', 'IR3', 'IRI', 'IUM', 'K', 'KO4', 'LA', 'LCO', 'LCP', 'LI', 'LU', 'MAC', 'MG', 'MH2', 'MH3', 'MLI', 'MMC', 'MN', 'MN3', 'MN5', 'MN6', 'MO1', 'MO2', 'MO3', 'MO4', 'MO5', 'MO6', 'MOO', 'MOS', 'MOW', 'MW1', 'MW2', 'MW3', 'NA', 'NA2', 'NA5', 'NA6', 'NAO', 'NAW', 'ND', 'NET', 'NH4', 'NI', 'NI1', 'NI2', 'NI3', 'NO2', 'NO3', 'NRU', 'O4M', 'OAA', 'OC1', 'OC2', 'OC3', 'OC4', 'OC5', 'OC6', 'OC7', 'OC8', 'OCL', 'OCM', 'OCN', 'OCO', 'OF1', 'OF2', 'OF3', 'OH', 'OS', 'OS4', 'OXL', 'PB', 'PBM', 'PD', 'PDV', 'PER', 'PI', 'PO3', 'PO4', 'PR', 'PT', 'PT4', 'PTN', 'RB', 'RH3', 'RHD', 'RU', 'SB', 'SCN', 'SE4', 'SEK', 'SM', 'SMO', 'SO3', 'SO4', 'SR', 'T1A', 'TB', 'TBA', 'TCN', 'TEA', 'TH', 'THE', 'TL', 'TMA', 'TRA', 'UNX', 'V', 'VN3', 'VO4', 'W', 'WO5', 'Y1', 'YB', 'YB2', 'YH', 'YT3', 'ZCM', 'ZN', 'ZN2', 'ZN3', 'ZNO', 'ZO3', 'ZR', 'NCO', 'OHX']);
|
||||
export const IonNames = new Set(['118', '119', '543', '1AL', '1CU', '2FK', '2HP', '2OF', '3CO', '3MT', '3NI', '3OF', '3P8', '4MO', '4PU', '4TI', '6MO', 'ACT', 'AG', 'AL', 'ALF', 'AM', 'ATH', 'AU', 'AU3', 'AUC', 'AZI', 'BA', 'BCT', 'BEF', 'BF4', 'BO4', 'BR', 'BS3', 'BSY', 'CA', 'CAC', 'CD', 'CD1', 'CD3', 'CD5', 'CE', 'CF', 'CHT', 'CL', 'CO', 'CO3', 'CO5', 'CON', 'CR', 'CS', 'CSB', 'CU', 'CU1', 'CU3', 'CUA', 'CUZ', 'CYN', 'DME', 'DMI', 'DSC', 'DTI', 'DY', 'E4N', 'EDR', 'EMC', 'ER3', 'EU', 'EU3', 'F', 'FE', 'FE2', 'FPO', 'GA', 'GD3', 'GEP', 'HAI', 'HG', 'HGC', 'IN', 'IOD', 'IR', 'IR3', 'IRI', 'IUM', 'K', 'KO4', 'LA', 'LCO', 'LCP', 'LI', 'LU', 'MAC', 'MG', 'MH2', 'MH3', 'MLI', 'MMC', 'MN', 'MN3', 'MN5', 'MN6', 'MO1', 'MO2', 'MO3', 'MO4', 'MO5', 'MO6', 'MOO', 'MOS', 'MOW', 'MW1', 'MW2', 'MW3', 'NA', 'NA2', 'NA5', 'NA6', 'NAO', 'NAW', 'ND', 'NET', 'NH4', 'NI', 'NI1', 'NI2', 'NI3', 'NO2', 'NO3', 'NRU', 'NT3', 'O4M', 'OAA', 'OC1', 'OC2', 'OC3', 'OC4', 'OC5', 'OC6', 'OC7', 'OC8', 'OCL', 'OCM', 'OCN', 'OCO', 'OF1', 'OF2', 'OF3', 'OH', 'OS', 'OS4', 'OXL', 'PB', 'PBM', 'PD', 'PDV', 'PER', 'PI', 'PO3', 'PO4', 'PR', 'PT', 'PT4', 'PTN', 'RB', 'RH3', 'RHD', 'RHF', 'RU', 'SB', 'SCN', 'SE4', 'SEK', 'SM', 'SMO', 'SO3', 'SO4', 'SR', 'T1A', 'TB', 'TBA', 'TCN', 'TEA', 'TH', 'THE', 'TL', 'TMA', 'TRA', 'UNX', 'V', 'VN3', 'VO4', 'W', 'WO5', 'Y1', 'YB', 'YB2', 'YH', 'YT3', 'ZCM', 'ZN', 'ZN2', 'ZN3', 'ZNO', 'ZO3', 'ZR', 'NCO', 'OHX']);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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 { Box3D, GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
@@ -1213,25 +1213,20 @@ 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;
|
||||
for (const unitA of structure.units) {
|
||||
if (!validUnit(unitA)) continue;
|
||||
|
||||
const bs = unit.boundary.sphere;
|
||||
Box3D.expand(bbox, unit.boundary.box, rvec);
|
||||
Vec3.transformMat4(imageCenter, bs.center, unit.conformation.operator.matrix);
|
||||
const bs = unitA.boundary.sphere;
|
||||
Vec3.transformMat4(imageCenter, bs.center, unitA.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 (unit.id >= other.id) continue;
|
||||
const unitB = structure.units[closeUnits.indices[i]];
|
||||
if (unitA.id >= unitB.id) continue;
|
||||
if (!validUnit(unitB) || !validUnitPair(unitA, unitB)) continue;
|
||||
|
||||
if (other.elements.length > 3 && !Box3D.overlaps(bbox, other.boundary.box)) continue;
|
||||
if (!validUnit(other) || !validUnitPair(unit, other)) continue;
|
||||
|
||||
if (other.elements.length >= unit.elements.length) callback(unit, other);
|
||||
else callback(other, unit);
|
||||
if (unitB.elements.length >= unitA.elements.length) callback(unitA, unitB);
|
||||
else callback(unitB, unitA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,10 +243,8 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
|
||||
...p,
|
||||
validUnit: (props && props.validUnit) || (u => Unit.isAtomic(u)),
|
||||
validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
|
||||
// In case both units have a struct conn record, ignore other criteria
|
||||
if (hasCommonStructConnRecord(a, b)) {
|
||||
return Structure.validUnitPair(s, a, b);
|
||||
}
|
||||
const isValidPair = Structure.validUnitPair(s, a, b);
|
||||
if (!isValidPair) return false;
|
||||
|
||||
const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
|
||||
const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;
|
||||
@@ -258,7 +256,13 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
|
||||
const notIonA = (!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Ion);
|
||||
const notIonB = (!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Ion);
|
||||
const notIon = notIonA && notIonB;
|
||||
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
|
||||
|
||||
const check = (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
|
||||
if (!check) {
|
||||
// In case both units have a struct conn record, ignore other criteria
|
||||
return hasCommonStructConnRecord(a, b);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 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>
|
||||
@@ -94,6 +94,8 @@ export namespace StructureRepresentationPresetProvider {
|
||||
}
|
||||
|
||||
export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
|
||||
if (!plugin.state.hasBehavior(StructureFocusRepresentation)) return;
|
||||
|
||||
return plugin.state.updateBehavior(StructureFocusRepresentation, p => {
|
||||
const c = createStructureColorThemeParams(plugin, structure, 'ball-and-stick', themeName || 'element-symbol', themeParams);
|
||||
p.surroundingsParams.colorTheme = c;
|
||||
|
||||
@@ -94,6 +94,8 @@ export class LociLabelManager {
|
||||
|
||||
constructor(public ctx: PluginContext) {
|
||||
ctx.managers.interactivity.lociHighlights.addProvider((loci, action, noRender) => {
|
||||
if (this.providers.length === 0) return;
|
||||
|
||||
this.mark(loci, action);
|
||||
if (!noRender) this.showLabels();
|
||||
});
|
||||
|
||||
@@ -29,9 +29,11 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
static DefaultNextSnapshotDelayInMs = 1500;
|
||||
|
||||
private entryMap = new Map<string, PluginStateSnapshotManager.Entry>();
|
||||
private defaultSnapshotId: UUID | undefined = undefined;
|
||||
|
||||
readonly events = {
|
||||
changed: this.ev()
|
||||
changed: this.ev(),
|
||||
opened: this.ev(),
|
||||
};
|
||||
|
||||
getIndex(e: PluginStateSnapshotManager.Entry) {
|
||||
@@ -66,6 +68,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
const old = this.getEntry(id);
|
||||
if (!old) return;
|
||||
|
||||
this.defaultSnapshotId = undefined;
|
||||
|
||||
if (old?.image) this.plugin.managers.asset.delete(old.image);
|
||||
const idx = this.getIndex(old);
|
||||
// The id changes here!
|
||||
@@ -171,20 +175,27 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
}
|
||||
|
||||
private async syncCurrent(options?: { name?: string, description?: string, params?: PluginState.SnapshotParams }) {
|
||||
const isEmpty = this.state.entries.size === 0;
|
||||
const canReplace = this.state.entries.size === 1 && this.state.current && this.state.current === this.defaultSnapshotId;
|
||||
|
||||
if (!isEmpty && !canReplace) return;
|
||||
|
||||
const snapshot = this.plugin.state.getSnapshot(options?.params);
|
||||
if (this.state.entries.size === 0 || !this.state.current) {
|
||||
this.add(PluginStateSnapshotManager.Entry(snapshot, { name: options?.name, description: options?.description }));
|
||||
} else {
|
||||
const image = (options?.params?.image ?? this.plugin.state.snapshotParams.value.image) ? await PluginStateSnapshotManager.getCanvasImageAsset(this.plugin, `${snapshot.id}-image.png`) : undefined;
|
||||
|
||||
if (isEmpty) {
|
||||
this.add(PluginStateSnapshotManager.Entry(snapshot, { name: options?.name, description: options?.description, image }));
|
||||
} else if (canReplace) {
|
||||
// Replace the current state only if there is a single snapshot that has been created automatically
|
||||
const current = this.getEntry(this.state.current);
|
||||
if (current?.image) this.plugin.managers.asset.delete(current.image);
|
||||
const image = (options?.params?.image ?? this.plugin.state.snapshotParams.value.image) ? await PluginStateSnapshotManager.getCanvasImageAsset(this.plugin, `${snapshot.id}-image.png`) : undefined;
|
||||
// TODO: this replaces the current snapshot which is not always intended
|
||||
this.replace(this.state.current, snapshot, { image });
|
||||
this.replace(this.state.current!, snapshot, { image });
|
||||
}
|
||||
|
||||
this.defaultSnapshotId = snapshot.id;
|
||||
}
|
||||
|
||||
async getStateSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean, params?: PluginState.SnapshotParams }): Promise<PluginStateSnapshotManager.StateSnapshot> {
|
||||
// TODO: diffing and all that fancy stuff
|
||||
await this.syncCurrent(options);
|
||||
|
||||
return {
|
||||
@@ -242,11 +253,11 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
const snapshot = JSON.parse(data);
|
||||
|
||||
if (PluginStateSnapshotManager.isStateSnapshot(snapshot)) {
|
||||
return this.setStateSnapshot(snapshot);
|
||||
await this.setStateSnapshot(snapshot);
|
||||
} else if (PluginStateSnapshotManager.isStateSnapshot(snapshot.data)) {
|
||||
return this.setStateSnapshot(snapshot.data);
|
||||
await this.setStateSnapshot(snapshot.data);
|
||||
} else {
|
||||
this.plugin.state.setSnapshot(snapshot);
|
||||
await this.plugin.state.setSnapshot(snapshot);
|
||||
}
|
||||
} else {
|
||||
const data = await this.plugin.runTask(readFromFile(file, 'zip'));
|
||||
@@ -270,8 +281,9 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
}
|
||||
|
||||
const snapshot = JSON.parse(stateData);
|
||||
return this.setStateSnapshot(snapshot);
|
||||
await this.setStateSnapshot(snapshot);
|
||||
}
|
||||
this.events.opened.next(void 0);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.plugin.log.error('Error reading state');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 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,7 +12,7 @@ import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { QueryContext, Structure, StructureElement, StructureQuery, StructureSelection } from '../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { StateObjectRef, StateSelection } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { structureElementStatsLabel } from '../../../mol-theme/label';
|
||||
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
|
||||
@@ -35,6 +35,13 @@ const HISTORY_CAPACITY = 24;
|
||||
|
||||
export type StructureSelectionModifier = 'add' | 'remove' | 'intersect' | 'set'
|
||||
|
||||
export type StructureSelectionSnapshot = {
|
||||
entries: {
|
||||
ref: string
|
||||
bundle: StructureElement.Bundle
|
||||
}[]
|
||||
}
|
||||
|
||||
export class StructureSelectionManager extends StatefulPluginComponent<StructureSelectionManagerState> {
|
||||
readonly events = {
|
||||
changed: this.ev<undefined>(),
|
||||
@@ -484,6 +491,31 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
}
|
||||
}
|
||||
|
||||
getSnapshot(): StructureSelectionSnapshot {
|
||||
const entries: StructureSelectionSnapshot['entries'] = [];
|
||||
|
||||
this.entries.forEach((entry, ref) => {
|
||||
entries.push({
|
||||
ref,
|
||||
bundle: StructureElement.Bundle.fromLoci(entry.selection)
|
||||
});
|
||||
});
|
||||
|
||||
return { entries };
|
||||
}
|
||||
|
||||
setSnapshot(snapshot: StructureSelectionSnapshot) {
|
||||
this.entries.clear();
|
||||
|
||||
for (const { ref, bundle } of snapshot.entries) {
|
||||
const structure = this.plugin.state.data.select(StateSelection.Generators.byRef(ref))[0]?.obj?.data as Structure;
|
||||
if (!structure) continue;
|
||||
|
||||
const loci = StructureElement.Bundle.toLoci(bundle, structure);
|
||||
this.fromLoci('set', loci, false);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super({ entries: new Map(), additionsHistory: [], stats: SelectionStats() });
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 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>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { parseDcd } from '../../mol-io/reader/dcd/parser';
|
||||
@@ -12,7 +13,7 @@ import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
|
||||
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
|
||||
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
|
||||
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromCCD, trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
|
||||
import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
|
||||
import { Coordinates, Model, Queries, QueryContext, Structure, StructureElement, StructureQuery, StructureSelection as Sel, Topology, ArrayTrajectory, Trajectory } from '../../mol-model/structure';
|
||||
@@ -272,14 +273,18 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
|
||||
params(a) {
|
||||
if (!a) {
|
||||
return {
|
||||
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header parameter and parse all datablocks into a single trajectory.' })),
|
||||
blockHeader: PD.Optional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.', hideIf: p => p.loadAllBlocks === true })),
|
||||
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header and Block Index parameters and parse all datablocks into a single trajectory.' })),
|
||||
blockHeader: PD.Optional(PD.Text(void 0, { description: 'Header of the block to parse. If not specifed, Block Index parameter applies.', hideIf: p => p.loadAllBlocks === true })),
|
||||
blockIndex: PD.Optional(PD.Numeric(0, { min: 0, step: 1 }, { description: 'Zero-based index of the block to parse. Only applies when Block Header parameter is not specified.', hideIf: p => p.loadAllBlocks === true || p.blockHeader })),
|
||||
};
|
||||
}
|
||||
const { blocks } = a.data;
|
||||
const headers = blocks.map(b => [b.header, b.header] as [string, string]);
|
||||
headers.push(['', '[Use Block Index]']);
|
||||
return {
|
||||
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header parameter and parse all data blocks into a single trajectory.' })),
|
||||
blockHeader: PD.Optional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse', hideIf: p => p.loadAllBlocks === true })),
|
||||
loadAllBlocks: PD.Optional(PD.Boolean(false, { description: 'If True, ignore Block Header and Block Index parameters and parse all data blocks into a single trajectory.' })),
|
||||
blockHeader: PD.Optional(PD.Select(blocks[0] && blocks[0].header, headers, { description: 'Header of the block to parse. If not specifed, Block Index parameter applies.', hideIf: p => p.loadAllBlocks === true })),
|
||||
blockIndex: PD.Optional(PD.Numeric(0, { min: 0, step: 1, max: blocks.length - 1 }, { description: 'Zero-based index of the block to parse. Only applies when Block Header parameter is not specified.', hideIf: p => p.loadAllBlocks === true || p.blockHeader })),
|
||||
};
|
||||
}
|
||||
})({
|
||||
@@ -300,10 +305,11 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
trajectory = new ArrayTrajectory(models);
|
||||
} else {
|
||||
const header = params.blockHeader || a.data.blocks[0].header;
|
||||
const header = params.blockHeader || a.data.blocks[params.blockIndex ?? 0].header;
|
||||
const block = a.data.blocks.find(b => b.header === header);
|
||||
if (!block) throw new Error(`Data block '${[header]}' not found.`);
|
||||
trajectory = await trajectoryFromMmCIF(block).runInContext(ctx);
|
||||
const isCcd = block.categoryNames.includes('chem_comp_atom') && !block.categoryNames.includes('atom_site') && !block.categoryNames.includes('ihm_sphere_obj_site') && !block.categoryNames.includes('ihm_gaussian_obj_site');
|
||||
trajectory = isCcd ? await trajectoryFromCCD(block).runInContext(ctx) : await trajectoryFromMmCIF(block, a.data).runInContext(ctx);
|
||||
}
|
||||
if (trajectory.frameCount === 0) throw new Error('No models found.');
|
||||
const props = trajectoryProps(trajectory);
|
||||
@@ -1086,7 +1092,7 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({
|
||||
from: SO.Format.Ply,
|
||||
to: SO.Shape.Provider,
|
||||
params(a) {
|
||||
return { };
|
||||
return {};
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
|
||||
@@ -172,6 +172,8 @@ const _VisibilityOutlined = <svg width='24px' height='24px' viewBox='0 0 24 24'>
|
||||
export function VisibilityOutlinedSvg() { return _VisibilityOutlined; }
|
||||
const _Warning = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z' /></svg>;
|
||||
export function WarningSvg() { return _Warning; }
|
||||
const _ContentCut = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M 9.64 7.64 c 0.23 -0.5 0.36 -1.05 0.36 -1.64 c 0 -2.21 -1.79 -4 -4 -4 S 2 3.79 2 6 s 1.79 4 4 4 c 0.59 0 1.14 -0.13 1.64 -0.36 L 10 12 l -2.36 2.36 C 7.14 14.13 6.59 14 6 14 c -2.21 0 -4 1.79 -4 4 s 1.79 4 4 4 s 4 -1.79 4 -4 c 0 -0.59 -0.13 -1.14 -0.36 -1.64 L 12 14 l 7 7 h 3 v -1 L 9.64 7.64 Z M 6 8 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 0 12 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 6 -7.5 c -0.28 0 -0.5 -0.22 -0.5 -0.5 s 0.22 -0.5 0.5 -0.5 s 0.5 0.22 0.5 0.5 s -0.22 0.5 -0.5 0.5 Z M 19 3 l -6 6 l 2 2 l 7 -7 V 3 Z' /></svg>;
|
||||
export function ContentCutSvg() { return _ContentCut; }
|
||||
|
||||
// Aliases
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 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>
|
||||
@@ -23,12 +23,46 @@ import { Asset } from '../mol-util/assets';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { useBehavior } from './hooks/use-behavior';
|
||||
|
||||
export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> {
|
||||
render() {
|
||||
return <PluginReactContext.Provider value={this.props.plugin}>
|
||||
export function Plugin({ plugin }: { plugin: PluginUIContext }) {
|
||||
if (plugin.isInitialized) {
|
||||
return <PluginReactContext.Provider value={plugin}>
|
||||
<Layout />
|
||||
</PluginReactContext.Provider>;
|
||||
}
|
||||
|
||||
return <PluginInitWrapper plugin={plugin} />;
|
||||
}
|
||||
|
||||
type LoadState =
|
||||
| { kind: 'initialized' }
|
||||
| { kind: 'pending' }
|
||||
| { kind: 'error', message: string }
|
||||
|
||||
function PluginInitWrapper({ plugin }: { plugin: PluginUIContext }) {
|
||||
const [state, setState] = React.useState<LoadState>({ kind: 'pending' });
|
||||
React.useEffect(() => {
|
||||
setState({ kind: 'pending' });
|
||||
let mounted = true;
|
||||
|
||||
plugin.initialized.then(() => {
|
||||
if (mounted) setState({ kind: 'initialized' });
|
||||
}).catch(err => {
|
||||
if (mounted) setState({ kind: 'error', message: `${err}` });
|
||||
});
|
||||
|
||||
return () => { mounted = false; };
|
||||
}, [plugin]);
|
||||
|
||||
if (state.kind === 'pending') return null;
|
||||
if (state.kind === 'error') {
|
||||
return <div className='msp-plugin'>
|
||||
<div className='msp-plugin-init-error'>Initialization error: {state.message}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <PluginReactContext.Provider value={plugin}>
|
||||
<Layout />
|
||||
</PluginReactContext.Provider>;
|
||||
}
|
||||
|
||||
export class PluginContextContainer extends React.Component<{ plugin: PluginUIContext, children?: any }> {
|
||||
|
||||
@@ -29,5 +29,10 @@
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
.msp-plugin-init-error {
|
||||
white-space: pre;
|
||||
margin: $control-spacing;
|
||||
}
|
||||
|
||||
background: $default-background;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ class ToastEntry extends PluginUIComponent<{ entry: PluginToastManager.Entry }>
|
||||
const entry = this.props.entry;
|
||||
const message = typeof entry.message === 'string'
|
||||
? <div dangerouslySetInnerHTML={{ __html: entry.message }} />
|
||||
// @ts-ignore // TODO: handle type better
|
||||
: <div><entry.message /></div>;
|
||||
|
||||
return <div className='msp-toast-entry'>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user