mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 07:04:22 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5664e1d8be | ||
|
|
4881a41256 | ||
|
|
70e07be64d | ||
|
|
8147b3aa34 | ||
|
|
b21552ff36 | ||
|
|
c683cbe962 | ||
|
|
bd270e4428 | ||
|
|
23d942d8a5 | ||
|
|
cbcd6b99d2 | ||
|
|
ee5c098a9f | ||
|
|
cd872b47e6 | ||
|
|
2683c5b318 | ||
|
|
c71f60a164 | ||
|
|
881cbc1947 | ||
|
|
f3e7febbd1 | ||
|
|
e68ad13031 | ||
|
|
7fbbe1e63a | ||
|
|
a5ca72af3c | ||
|
|
1ce6641eb3 | ||
|
|
5dc413ab8c | ||
|
|
50b615e86c | ||
|
|
5b4c6743e7 | ||
|
|
99a3906978 | ||
|
|
981db34736 | ||
|
|
c079a8c5a8 | ||
|
|
ad70adf6ce | ||
|
|
89110b52bd | ||
|
|
8a69f050a6 | ||
|
|
9e38a44406 | ||
|
|
3514ab23c3 | ||
|
|
b59e3c383d | ||
|
|
eeba565d78 | ||
|
|
687e54cc87 | ||
|
|
ac73939440 | ||
|
|
7a3eb8d03f | ||
|
|
3d26904e0b | ||
|
|
468e14bc35 | ||
|
|
e2dc61212e | ||
|
|
aa911ad4bc | ||
|
|
bb5494264c | ||
|
|
c0116a3baa | ||
|
|
9c7497b447 | ||
|
|
fa3a79fdeb | ||
|
|
2987240df4 |
25493
package-lock.json
generated
25493
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
76
package.json
76
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.7",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -86,64 +86,64 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.17.7",
|
||||
"@graphql-codegen/cli": "^1.17.8",
|
||||
"@graphql-codegen/time": "^1.17.10",
|
||||
"@graphql-codegen/typescript": "^1.17.9",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.17.8",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.17.7",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.8",
|
||||
"@types/cors": "^2.8.7",
|
||||
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"@graphql-codegen/add": "^2.0.2",
|
||||
"@graphql-codegen/cli": "^1.19.4",
|
||||
"@graphql-codegen/time": "^2.0.2",
|
||||
"@graphql-codegen/typescript": "^1.19.0",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.12",
|
||||
"@types/cors": "^2.8.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.9.1",
|
||||
"@typescript-eslint/parser": "^4.9.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^5.3.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^7.8.1",
|
||||
"cpx2": "^3.0.0",
|
||||
"css-loader": "^5.0.1",
|
||||
"eslint": "^7.15.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"graphql": "^15.3.0",
|
||||
"graphql": "^15.4.0",
|
||||
"http-server": "^0.12.3",
|
||||
"jest": "^26.4.2",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"raw-loader": "^4.0.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^2.20.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"ts-jest": "^26.3.0",
|
||||
"typescript": "^4.0.2",
|
||||
"jest": "^26.6.3",
|
||||
"mini-css-extract-plugin": "^1.3.2",
|
||||
"node-sass": "^5.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^10.1.0",
|
||||
"simple-git": "^2.25.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^4.1.2",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.33",
|
||||
"@types/benchmark": "^2.1.0",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/jest": "^25.2.3",
|
||||
"@types/node": "^14.10.1",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/jest": "^26.0.18",
|
||||
"@types/node": "^14.14.11",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/swagger-ui-dist": "3.30.0",
|
||||
"argparse": "^1.0.10",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^7.0.9",
|
||||
"immer": "^8.0.0",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"rxjs": "^6.6.3",
|
||||
"swagger-ui-dist": "^3.33.0",
|
||||
"tslib": "^2.0.1",
|
||||
"swagger-ui-dist": "^3.37.2",
|
||||
"tslib": "^2.0.3",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
});
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -68,7 +68,7 @@ const DefaultViewerOptions = {
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
disableAntialiasing: false,
|
||||
pixelScale: void 0 as number | undefined,
|
||||
pixelScale: 1,
|
||||
enableWboit: false,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
@@ -115,16 +115,11 @@ export class Viewer {
|
||||
components: {
|
||||
...DefaultPluginSpec.components,
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
viewport: {
|
||||
canvas3d: {
|
||||
multiSample: { mode: 'on', sampleLevel: 2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.EnableWboit, true || o.enableWboit],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
@@ -170,13 +165,13 @@ export class Viewer {
|
||||
}));
|
||||
}
|
||||
|
||||
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false) {
|
||||
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true });
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
|
||||
}
|
||||
|
||||
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
|
||||
|
||||
@@ -26,7 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
case 'value-ref': return `Reference to a state object.`;
|
||||
case 'value-ref': return `Reference to a runtime defined value.`;
|
||||
case 'data-ref': return `Reference to a computed data value.`;
|
||||
case 'text': return 'String';
|
||||
case 'interval': return `Interval [min, max]`;
|
||||
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,8 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Basis } from '../../extensions/alpha-orbitals/cubes';
|
||||
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/orbitals';
|
||||
import { CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
|
||||
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
|
||||
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
|
||||
import { createPluginAsync, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
@@ -20,23 +19,33 @@ import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import './index.html';
|
||||
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
interface DemoInput {
|
||||
moleculeSdf: string,
|
||||
basis: Basis,
|
||||
order: SphericalBasisOrder,
|
||||
orbitals: {
|
||||
energy: number,
|
||||
alpha: number[]
|
||||
}[]
|
||||
orbitals: AlphaOrbital[]
|
||||
}
|
||||
|
||||
interface Params {
|
||||
orbitalIndex: number,
|
||||
show: { name: 'orbital', params: { index: number } } | { name: 'density', params: {} },
|
||||
isoValue: number,
|
||||
gpuSurface: boolean,
|
||||
cpuCompute: boolean
|
||||
gpuSurface: boolean
|
||||
}
|
||||
|
||||
type Selectors = {
|
||||
type: 'orbital',
|
||||
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>,
|
||||
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
|
||||
negative: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
|
||||
} | {
|
||||
type: 'density',
|
||||
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalDensityVolume>,
|
||||
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
|
||||
}
|
||||
|
||||
export class AlphaOrbitalsExample {
|
||||
@@ -62,6 +71,14 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
@@ -71,40 +88,84 @@ export class AlphaOrbitalsExample {
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
readonly state = new BehaviorSubject<Params>({ orbitalIndex: 32, isoValue: 1, gpuSurface: false, cpuCompute: false });
|
||||
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
|
||||
|
||||
private selectors?: Selectors = void 0;
|
||||
private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;
|
||||
|
||||
private volume?: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>;
|
||||
private positive?: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>;
|
||||
private negative?: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>;
|
||||
private currentParams: Params = { ...this.state.value };
|
||||
|
||||
private async setIndex() {
|
||||
if (!this.volume?.isOk) return;
|
||||
private clearVolume() {
|
||||
if (!this.selectors) return;
|
||||
const v = this.selectors.volume;
|
||||
this.selectors = void 0;
|
||||
return this.plugin.build().delete(v).commit();
|
||||
}
|
||||
|
||||
private async syncVolume() {
|
||||
if (!this.basis?.isOk) return;
|
||||
|
||||
const state = this.state.value;
|
||||
await this.plugin.build().to(this.volume).update(CreateOrbitalVolume, () => ({ index: state.orbitalIndex, cpuCompute: state.cpuCompute })).commit();
|
||||
|
||||
if (state.show.name !== this.selectors?.type) {
|
||||
await this.clearVolume();
|
||||
}
|
||||
|
||||
const update = this.plugin.build();
|
||||
if (state.show.name === 'orbital') {
|
||||
if (!this.selectors) {
|
||||
const volume = update
|
||||
.to(this.basis)
|
||||
.apply(CreateOrbitalVolume, { index: state.show.params.index });
|
||||
|
||||
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
|
||||
const negative = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('negative', ColorNames.red)).selector;
|
||||
|
||||
this.selectors = { type: 'orbital', volume: volume.selector, positive, negative };
|
||||
} else {
|
||||
const index = state.show.params.index;
|
||||
update.to(this.selectors.volume).update(CreateOrbitalVolume, () => ({ index }));
|
||||
}
|
||||
} else {
|
||||
if (!this.selectors) {
|
||||
const volume = update
|
||||
.to(this.basis)
|
||||
.apply(CreateOrbitalDensityVolume);
|
||||
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
|
||||
this.selectors = { type: 'density', volume: volume.selector, positive };
|
||||
}
|
||||
}
|
||||
|
||||
await update.commit();
|
||||
|
||||
if (this.currentParams.gpuSurface !== this.state.value.gpuSurface) {
|
||||
await this.setIsovalue();
|
||||
}
|
||||
|
||||
this.currentParams = this.state.value;
|
||||
}
|
||||
|
||||
private setIsovalue() {
|
||||
if (!this.selectors) return;
|
||||
|
||||
this.currentParams = this.state.value;
|
||||
const update = this.plugin.build();
|
||||
update.to(this.positive!).update(this.volumeParams('positive', ColorNames.blue));
|
||||
update.to(this.negative!).update(this.volumeParams('negative', ColorNames.red));
|
||||
update.to(this.selectors.positive).update(this.volumeParams('positive', ColorNames.blue));
|
||||
if (this.selectors?.type === 'orbital') {
|
||||
update.to(this.selectors.negative).update(this.volumeParams('negative', ColorNames.red));
|
||||
}
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
private volumeParams(kind: 'positive' | 'negative', color: Color): StateTransformer.Params<typeof CreateOrbitalRepresentation3D> {
|
||||
return {
|
||||
alpha: 1.0,
|
||||
alpha: 0.85,
|
||||
color,
|
||||
directVolume: this.state.value.gpuSurface,
|
||||
kind,
|
||||
relativeIsovalue: this.state.value.isoValue,
|
||||
pickable: false,
|
||||
xrayShaded: false
|
||||
xrayShaded: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,36 +180,28 @@ export class AlphaOrbitalsExample {
|
||||
const all = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'all');
|
||||
if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
|
||||
|
||||
const state = this.state.value;
|
||||
|
||||
this.volume = await this.plugin.build().toRoot()
|
||||
this.basis = await this.plugin.build().toRoot()
|
||||
.apply(StaticBasisAndOrbitals, { basis: input.basis, order: input.order, orbitals: input.orbitals })
|
||||
.apply(CreateOrbitalVolume, { index: state.orbitalIndex, forceCpuCompute: this.currentParams.cpuCompute })
|
||||
.commit();
|
||||
|
||||
if (!this.volume.isOk) {
|
||||
this.volume = void 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const repr = this.plugin.build().to(this.volume);
|
||||
|
||||
this.positive = repr.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
|
||||
this.negative = repr.apply(CreateOrbitalRepresentation3D, this.volumeParams('negative', ColorNames.red)).selector;
|
||||
|
||||
await repr.commit();
|
||||
await this.syncVolume();
|
||||
|
||||
this.params.next({
|
||||
orbitalIndex: ParamDefinition.Numeric(this.currentParams.orbitalIndex, { min: 0, max: input.orbitals.length - 1 }, { immediateUpdate: true, isEssential: true }),
|
||||
show: ParamDefinition.MappedStatic('orbital', {
|
||||
'orbital': ParamDefinition.Group({
|
||||
index: ParamDefinition.Numeric(32, { min: 0, max: input.orbitals.length - 1 }, { immediateUpdate: true, isEssential: true }),
|
||||
}),
|
||||
'density': ParamDefinition.EmptyGroup()
|
||||
}, { cycle: true }),
|
||||
isoValue: ParamDefinition.Numeric(this.currentParams.isoValue, { min: 0.5, max: 3, step: 0.1 }, { immediateUpdate: true, isEssential: false }),
|
||||
gpuSurface: ParamDefinition.Boolean(this.currentParams.gpuSurface),
|
||||
cpuCompute: ParamDefinition.Boolean(this.currentParams.cpuCompute, { label: 'CPU Compute' })
|
||||
gpuSurface: ParamDefinition.Boolean(this.currentParams.gpuSurface, { isHidden: true })
|
||||
});
|
||||
|
||||
this.state.pipe(skip(1), debounceTime(1000 / 24)).subscribe(async params => {
|
||||
if (params.orbitalIndex !== this.currentParams.orbitalIndex
|
||||
|| params.cpuCompute !== this.currentParams.cpuCompute) {
|
||||
this.setIndex();
|
||||
if (params.show.name !== this.currentParams.show.name
|
||||
|| (params.show.name === 'orbital' && this.currentParams.show.name === 'orbital' && params.show.params.index !== this.currentParams.show.params.index)) {
|
||||
this.syncVolume();
|
||||
} else if (params.isoValue !== this.currentParams.isoValue || params.gpuSurface !== this.currentParams.gpuSurface) {
|
||||
this.setIsovalue();
|
||||
}
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { CollocationParams } from '../../extensions/alpha-orbitals/collocation';
|
||||
import { Basis } from '../../extensions/alpha-orbitals/cubes';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
|
||||
const _testBasis: Basis = {
|
||||
'atoms': [
|
||||
{
|
||||
'center': [
|
||||
0.025886090588624934,
|
||||
0.019164790004065606,
|
||||
-0.013539970104105408
|
||||
] as Vec3,
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
-0.004151277818987536,
|
||||
-0.02067024147993795,
|
||||
-0.05150303336984537,
|
||||
0.33462711739899537,
|
||||
0.5621061300983125,
|
||||
0.17129946969948573
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
152.28769660788095,
|
||||
27.928015215973073,
|
||||
7.848374792384515,
|
||||
1.1223350202705642,
|
||||
0.5093846587907856,
|
||||
0.24292266532510307
|
||||
]
|
||||
},
|
||||
{
|
||||
'angularMomentum': [1],
|
||||
'coefficients': [
|
||||
[
|
||||
0.007924233646294425,
|
||||
0.051441048251911314,
|
||||
0.18984000600705359,
|
||||
0.4049863191150474,
|
||||
0.40123628611490797,
|
||||
0.1051855189039082
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
27.203421487167727,
|
||||
7.09409912597673,
|
||||
2.5383362605345954,
|
||||
1.0610730767843852,
|
||||
0.4851948916410433,
|
||||
0.22938302550642545
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'center': [
|
||||
0.5082729578468134,
|
||||
1.6880351220025265,
|
||||
0.4963443067810461
|
||||
] as Vec3,
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
0.009163596280542963,
|
||||
0.04936149294292479,
|
||||
0.16853830490998634,
|
||||
0.37056279972195677,
|
||||
0.4164915298246781,
|
||||
0.13033408410772263
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
},
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
-0.32279868167000036,
|
||||
3.209629817295221,
|
||||
2.4672629224617935,
|
||||
-0.048487066612842224,
|
||||
-0.2611850111200143,
|
||||
-0.8917817597810863,
|
||||
-1.9607480081275706,
|
||||
-2.203769342520311,
|
||||
-0.6896328935259993
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
10.256286070314905,
|
||||
0.6227965325875392,
|
||||
0.2391007667853915,
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'center': [
|
||||
1.1367367844436005,
|
||||
-0.47018519422670163,
|
||||
-1.356802622574504
|
||||
] as Vec3,
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
0.009163596280542963,
|
||||
0.04936149294292479,
|
||||
0.16853830490998634,
|
||||
0.37056279972195677,
|
||||
0.4164915298246781,
|
||||
0.13033408410772263
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
},
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
-0.32279868167000036,
|
||||
3.209629817295221,
|
||||
2.4672629224617935,
|
||||
-0.048487066612842224,
|
||||
-0.2611850111200143,
|
||||
-0.8917817597810863,
|
||||
-1.9607480081275706,
|
||||
-2.203769342520311,
|
||||
-0.6896328935259993
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
10.256286070314905,
|
||||
0.6227965325875392,
|
||||
0.2391007667853915,
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const grid = {
|
||||
box: Box3D.create(Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)),
|
||||
delta: Vec3.create(2, 2, 2),
|
||||
dimensions: Vec3.create(2, 2, 2),
|
||||
npoints: 8,
|
||||
size: Vec3.create(2, 2, 2)
|
||||
};
|
||||
|
||||
export const TestWaterParams: CollocationParams = {
|
||||
grid,
|
||||
alphaOrbitals: [-2.2623991420609075e-16, 0.6360205395000592, 0.6672122399886391, -0.3876927909355508, -1.6780131293332933e-16, 2.844782862661151e-16, 4.977960694176068e-19, -2.3945919908996803e-16],
|
||||
basis: _testBasis,
|
||||
cutoffThreshold: 0,
|
||||
sphericalOrder: 'cca-reverse'
|
||||
};
|
||||
@@ -8,11 +8,18 @@ import { Box3D } from '../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { sphericalCollocation } from '../collocation';
|
||||
import { Basis } from '../cubes';
|
||||
import { Basis, CubeGridInfo } from '../data-model';
|
||||
|
||||
describe('alpha-orbitals-cubes', () => {
|
||||
it('water', async () => {
|
||||
const grid = {
|
||||
const grid: CubeGridInfo = {
|
||||
params: {
|
||||
basis: _testBasis,
|
||||
cutoffThreshold: 0,
|
||||
sphericalOrder: 'cca-reverse',
|
||||
boxExpand: 0,
|
||||
gridSpacing: []
|
||||
},
|
||||
box: Box3D.create(Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)),
|
||||
delta: Vec3.create(2, 2, 2),
|
||||
dimensions: Vec3.create(2, 2, 2),
|
||||
@@ -20,12 +27,10 @@ describe('alpha-orbitals-cubes', () => {
|
||||
size: Vec3.create(2, 2, 2)
|
||||
};
|
||||
|
||||
const matrix = await sphericalCollocation({
|
||||
grid,
|
||||
alphaOrbitals: [-2.2623991420609075e-16, 0.6360205395000592, 0.6672122399886391, -0.3876927909355508, -1.6780131293332933e-16, 2.844782862661151e-16, 4.977960694176068e-19, -2.3945919908996803e-16],
|
||||
basis: _testBasis,
|
||||
cutoffThreshold: 0,
|
||||
sphericalOrder: 'cca-reverse'
|
||||
const matrix = await sphericalCollocation(grid, {
|
||||
energy: 0,
|
||||
occupancy: 0,
|
||||
alpha: [-2.2623991420609075e-16, 0.6360205395000592, 0.6672122399886391, -0.3876927909355508, -1.6780131293332933e-16, 2.844782862661151e-16, 4.977960694176068e-19, -2.3945919908996803e-16]
|
||||
}, RuntimeContext.Synchronous);
|
||||
|
||||
const expected = [-0.1451730622877498, 0.06479453956039086, -0.2777738736440713, -0.057116584776260436, 0.05929916178822645, 0.2742903371231049, -0.07221698722165386, 0.15389180241391376];
|
||||
|
||||
@@ -9,31 +9,15 @@
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { arrayMin } from '../../mol-util/array';
|
||||
import { Basis, CubeGridInfo } from './cubes';
|
||||
import {
|
||||
normalizeBasicOrder,
|
||||
|
||||
SphericalBasisOrder, SphericalFunctions
|
||||
} from './orbitals';
|
||||
|
||||
export interface CollocationParams {
|
||||
grid: CubeGridInfo;
|
||||
basis: Basis;
|
||||
sphericalOrder: SphericalBasisOrder;
|
||||
cutoffThreshold: number;
|
||||
alphaOrbitals: number[];
|
||||
}
|
||||
import { AlphaOrbital, CubeGridInfo } from './data-model';
|
||||
import { normalizeBasicOrder, SphericalFunctions } from './spherical-functions';
|
||||
|
||||
export async function sphericalCollocation(
|
||||
{
|
||||
grid,
|
||||
basis,
|
||||
sphericalOrder,
|
||||
cutoffThreshold,
|
||||
alphaOrbitals,
|
||||
}: CollocationParams,
|
||||
grid: CubeGridInfo,
|
||||
orbital: AlphaOrbital,
|
||||
taskCtx: RuntimeContext
|
||||
) {
|
||||
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
|
||||
let baseCount = 0;
|
||||
|
||||
for (const atom of basis.atoms) {
|
||||
@@ -58,7 +42,7 @@ export async function sphericalCollocation(
|
||||
for (const L of shell.angularMomentum) {
|
||||
const alpha = normalizeBasicOrder(
|
||||
L,
|
||||
alphaOrbitals.slice(baseIndex, baseIndex + 2 * L + 1),
|
||||
orbital.alpha.slice(baseIndex, baseIndex + 2 * L + 1),
|
||||
sphericalOrder
|
||||
);
|
||||
baseIndex += 2 * L + 1;
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Inspired by https://github.com/dgasmith/gau2grid.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Grid } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
import { CollocationParams, sphericalCollocation } from './collocation';
|
||||
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
|
||||
import { SphericalBasisOrder } from './orbitals';
|
||||
|
||||
export interface CubeGridInfo {
|
||||
dimensions: Vec3;
|
||||
box: Box3D;
|
||||
size: Vec3;
|
||||
npoints: number;
|
||||
delta: Vec3;
|
||||
}
|
||||
|
||||
export interface CubeGrid {
|
||||
grid: Grid;
|
||||
isovalues?: { negative?: number; positive?: number };
|
||||
}
|
||||
|
||||
export interface Basis {
|
||||
atoms: {
|
||||
// in Bohr units!
|
||||
center: Vec3;
|
||||
shells: SphericalElectronShell[];
|
||||
}[];
|
||||
}
|
||||
|
||||
// Note: generally contracted gaussians are currently not supported.
|
||||
export interface SphericalElectronShell {
|
||||
exponents: number[];
|
||||
angularMomentum: number[];
|
||||
// number[] for each angular momentum
|
||||
coefficients: number[][];
|
||||
}
|
||||
|
||||
export interface SphericalCollocationParams {
|
||||
basis: Basis;
|
||||
/**
|
||||
* for each electron shell compute a cutoff radius as
|
||||
*
|
||||
* const cutoffRadius = Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents));
|
||||
*
|
||||
*/
|
||||
cutoffThreshold: number;
|
||||
sphericalOrder: SphericalBasisOrder;
|
||||
boxExpand: number;
|
||||
gridSpacing: number | [atomCountThreshold: number, spacing: number][];
|
||||
alphaOrbitals: number[];
|
||||
doNotComputeIsovalues?: boolean
|
||||
}
|
||||
|
||||
export function createSphericalCollocationGrid(
|
||||
params: SphericalCollocationParams, webgl?: WebGLContext
|
||||
): Task<CubeGrid> {
|
||||
return Task.create('Spherical Collocation Grid', async (ctx) => {
|
||||
const centers = params.basis.atoms.map(a => a.center);
|
||||
|
||||
const cParams: CollocationParams = {
|
||||
grid: initBox(centers, params.gridSpacing, params.boxExpand),
|
||||
basis: params.basis,
|
||||
alphaOrbitals: params.alphaOrbitals,
|
||||
cutoffThreshold: params.cutoffThreshold,
|
||||
sphericalOrder: params.sphericalOrder
|
||||
};
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cParams);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
// console.time('cpu');
|
||||
matrix = await sphericalCollocation(cParams, ctx);
|
||||
// console.timeEnd('cpu');
|
||||
}
|
||||
|
||||
return createCubeGrid(cParams.grid, matrix, [0, 1, 2], !params.doNotComputeIsovalues);
|
||||
});
|
||||
}
|
||||
|
||||
const BohrToAngstromFactor = 0.529177210859;
|
||||
|
||||
function createCubeGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[], computeIsovalues: boolean) {
|
||||
const boxSize = Box3D.size(Vec3(), gridInfo.box);
|
||||
const boxOrigin = Vec3.clone(gridInfo.box.min);
|
||||
|
||||
Vec3.scale(boxSize, boxSize, BohrToAngstromFactor);
|
||||
Vec3.scale(boxOrigin, boxOrigin, BohrToAngstromFactor);
|
||||
|
||||
const scale = Mat4.fromScaling(
|
||||
Mat4(),
|
||||
Vec3.div(
|
||||
Vec3(),
|
||||
boxSize,
|
||||
Vec3.sub(Vec3(), gridInfo.dimensions, Vec3.create(1, 1, 1))
|
||||
)
|
||||
);
|
||||
const translate = Mat4.fromTranslation(Mat4(), boxOrigin);
|
||||
const matrix = Mat4.mul(Mat4(), translate, scale);
|
||||
|
||||
const grid: Grid = {
|
||||
transform: { kind: 'matrix', matrix },
|
||||
cells: Tensor.create(
|
||||
Tensor.Space(gridInfo.dimensions, axisOrder, Float32Array),
|
||||
(values as any) as Tensor.Data
|
||||
),
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMax(values),
|
||||
sigma: arrayRms(values),
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: when using GPU rendering, the cumulative sum can be computed
|
||||
// along the ray on the fly?
|
||||
let isovalues: { negative?: number, positive?: number } | undefined;
|
||||
|
||||
if (computeIsovalues) {
|
||||
isovalues = computeIsocontourValues(values, 0.85);
|
||||
}
|
||||
|
||||
return { grid, isovalues };
|
||||
}
|
||||
|
||||
function initBox(
|
||||
geometry: Vec3[],
|
||||
spacing: SphericalCollocationParams['gridSpacing'],
|
||||
expand: number
|
||||
): CubeGridInfo {
|
||||
const count = geometry.length;
|
||||
const box = Box3D.expand(
|
||||
Box3D(),
|
||||
Box3D.fromVec3Array(Box3D(), geometry),
|
||||
Vec3.create(expand, expand, expand)
|
||||
);
|
||||
const size = Box3D.size(Vec3(), box);
|
||||
|
||||
const spacingThresholds =
|
||||
typeof spacing === 'number' ? [[0, spacing]] : [...spacing];
|
||||
spacingThresholds.sort((a, b) => b[0] - a[0]);
|
||||
|
||||
let s = 0.4;
|
||||
for (let i = 0; i <= spacingThresholds.length; i++) {
|
||||
s = spacingThresholds[i][1];
|
||||
if (spacingThresholds[i][0] <= count) break;
|
||||
}
|
||||
|
||||
const dimensions = Vec3.ceil(Vec3(), Vec3.scale(Vec3(), size, 1 / s));
|
||||
|
||||
return {
|
||||
box,
|
||||
dimensions,
|
||||
size,
|
||||
npoints: dimensions[0] * dimensions[1] * dimensions[2],
|
||||
delta: Vec3.div(Vec3(), size, Vec3.subScalar(Vec3(), dimensions, 1)),
|
||||
};
|
||||
}
|
||||
|
||||
export function computeIsocontourValues(
|
||||
input: Float32Array,
|
||||
cumulativeThreshold: number
|
||||
) {
|
||||
let weightSum = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
weightSum += w;
|
||||
}
|
||||
const avgWeight = weightSum / input.length;
|
||||
let minWeight = 3 * avgWeight;
|
||||
|
||||
// do not try to identify isovalues for degenerate data
|
||||
// e.g. all values are almost same
|
||||
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
|
||||
return { negative: void 0, positive: void 0 };
|
||||
}
|
||||
|
||||
let size = 0;
|
||||
while (true) {
|
||||
let csum = 0;
|
||||
size = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
if (w >= minWeight) {
|
||||
csum += w;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
if (csum / weightSum > cumulativeThreshold) {
|
||||
break;
|
||||
}
|
||||
|
||||
minWeight -= avgWeight;
|
||||
}
|
||||
|
||||
const values = new Float32Array(size);
|
||||
const weights = new Float32Array(size);
|
||||
const indices = new Int32Array(size);
|
||||
|
||||
let o = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
if (w >= minWeight) {
|
||||
values[o] = v;
|
||||
weights[o] = w;
|
||||
indices[o] = o;
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
sortArray(
|
||||
indices,
|
||||
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
|
||||
);
|
||||
|
||||
let cweight = 0,
|
||||
cutoffIndex = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
cweight += weights[indices[i]];
|
||||
|
||||
if (cweight / weightSum >= cumulativeThreshold) {
|
||||
cutoffIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let positive = Number.POSITIVE_INFINITY,
|
||||
negative = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < cutoffIndex; i++) {
|
||||
const v = values[indices[i]];
|
||||
if (v > 0) {
|
||||
if (v < positive) positive = v;
|
||||
} else if (v < 0) {
|
||||
if (v > negative) negative = v;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
|
||||
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
|
||||
};
|
||||
}
|
||||
131
src/extensions/alpha-orbitals/data-model.ts
Normal file
131
src/extensions/alpha-orbitals/data-model.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Grid } from '../../mol-model/volume';
|
||||
import { SphericalBasisOrder } from './spherical-functions';
|
||||
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
|
||||
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
|
||||
|
||||
// Note: generally contracted gaussians are currently not supported.
|
||||
export interface SphericalElectronShell {
|
||||
exponents: number[];
|
||||
angularMomentum: number[];
|
||||
// number[] for each angular momentum
|
||||
coefficients: number[][];
|
||||
}
|
||||
|
||||
export interface Basis {
|
||||
atoms: {
|
||||
// in Bohr units!
|
||||
center: Vec3;
|
||||
shells: SphericalElectronShell[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AlphaOrbital {
|
||||
energy: number;
|
||||
occupancy: number;
|
||||
alpha: number[];
|
||||
}
|
||||
|
||||
export interface CubeGridComputationParams {
|
||||
basis: Basis;
|
||||
/**
|
||||
* for each electron shell compute a cutoff radius as
|
||||
* const cutoffRadius = Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents));
|
||||
*/
|
||||
cutoffThreshold: number;
|
||||
sphericalOrder: SphericalBasisOrder;
|
||||
boxExpand: number;
|
||||
gridSpacing: number | [atomCountThreshold: number, spacing: number][];
|
||||
doNotComputeIsovalues?: boolean;
|
||||
}
|
||||
|
||||
export interface CubeGridInfo {
|
||||
params: CubeGridComputationParams;
|
||||
dimensions: Vec3;
|
||||
box: Box3D;
|
||||
size: Vec3;
|
||||
npoints: number;
|
||||
delta: Vec3;
|
||||
}
|
||||
|
||||
export interface CubeGrid {
|
||||
grid: Grid;
|
||||
isovalues?: { negative?: number; positive?: number };
|
||||
}
|
||||
|
||||
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
|
||||
const geometry = params.basis.atoms.map(a => a.center);
|
||||
const { gridSpacing: spacing, boxExpand: expand } = params;
|
||||
|
||||
const count = geometry.length;
|
||||
const box = Box3D.expand(
|
||||
Box3D(),
|
||||
Box3D.fromVec3Array(Box3D(), geometry),
|
||||
Vec3.create(expand, expand, expand)
|
||||
);
|
||||
const size = Box3D.size(Vec3(), box);
|
||||
|
||||
const spacingThresholds =
|
||||
typeof spacing === 'number' ? [[0, spacing]] : [...spacing];
|
||||
spacingThresholds.sort((a, b) => b[0] - a[0]);
|
||||
|
||||
let s = 0.4;
|
||||
for (let i = 0; i <= spacingThresholds.length; i++) {
|
||||
s = spacingThresholds[i][1];
|
||||
if (spacingThresholds[i][0] <= count) break;
|
||||
}
|
||||
|
||||
const dimensions = Vec3.ceil(Vec3(), Vec3.scale(Vec3(), size, 1 / s));
|
||||
|
||||
return {
|
||||
params,
|
||||
box,
|
||||
dimensions,
|
||||
size,
|
||||
npoints: dimensions[0] * dimensions[1] * dimensions[2],
|
||||
delta: Vec3.div(Vec3(), size, Vec3.subScalar(Vec3(), dimensions, 1)),
|
||||
};
|
||||
}
|
||||
|
||||
const BohrToAngstromFactor = 0.529177210859;
|
||||
|
||||
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
|
||||
const boxSize = Box3D.size(Vec3(), gridInfo.box);
|
||||
const boxOrigin = Vec3.clone(gridInfo.box.min);
|
||||
|
||||
Vec3.scale(boxSize, boxSize, BohrToAngstromFactor);
|
||||
Vec3.scale(boxOrigin, boxOrigin, BohrToAngstromFactor);
|
||||
|
||||
const scale = Mat4.fromScaling(
|
||||
Mat4(),
|
||||
Vec3.div(
|
||||
Vec3(),
|
||||
boxSize,
|
||||
Vec3.sub(Vec3(), gridInfo.dimensions, Vec3.create(1, 1, 1))
|
||||
)
|
||||
);
|
||||
const translate = Mat4.fromTranslation(Mat4(), boxOrigin);
|
||||
const matrix = Mat4.mul(Mat4(), translate, scale);
|
||||
|
||||
const grid: Grid = {
|
||||
transform: { kind: 'matrix', matrix },
|
||||
cells: Tensor.create(
|
||||
Tensor.Space(gridInfo.dimensions, axisOrder, Float32Array),
|
||||
(values as any) as Tensor.Data
|
||||
),
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values),
|
||||
},
|
||||
};
|
||||
|
||||
return grid;
|
||||
}
|
||||
124
src/extensions/alpha-orbitals/density.ts
Normal file
124
src/extensions/alpha-orbitals/density.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
|
||||
|
||||
export function createSphericalCollocationDensityGrid(
|
||||
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
|
||||
): Task<CubeGrid> {
|
||||
return Task.create('Spherical Collocation Grid', async (ctx) => {
|
||||
const cubeGrid = initCubeGrid(params);
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
throw new Error('Missing OES_texture_float WebGL extension.');
|
||||
}
|
||||
|
||||
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
|
||||
let isovalues: { negative?: number, positive?: number } | undefined;
|
||||
|
||||
if (!params.doNotComputeIsovalues) {
|
||||
isovalues = computeDensityIsocontourValues(matrix, 0.85);
|
||||
}
|
||||
|
||||
return { grid, isovalues };
|
||||
});
|
||||
}
|
||||
|
||||
export function computeDensityIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
|
||||
let weightSum = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = Math.abs(v);
|
||||
weightSum += w;
|
||||
}
|
||||
const avgWeight = weightSum / input.length;
|
||||
let minWeight = 3 * avgWeight;
|
||||
|
||||
// do not try to identify isovalues for degenerate data
|
||||
// e.g. all values are almost same
|
||||
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
|
||||
return { negative: void 0, positive: void 0 };
|
||||
}
|
||||
|
||||
let size = 0;
|
||||
while (true) {
|
||||
let csum = 0;
|
||||
size = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = Math.abs(v);
|
||||
if (w >= minWeight) {
|
||||
csum += w;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
if (csum / weightSum > cumulativeThreshold) {
|
||||
break;
|
||||
}
|
||||
|
||||
minWeight -= avgWeight;
|
||||
}
|
||||
|
||||
const values = new Float32Array(size);
|
||||
const weights = new Float32Array(size);
|
||||
const indices = new Int32Array(size);
|
||||
|
||||
let o = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = Math.abs(v);
|
||||
if (w >= minWeight) {
|
||||
values[o] = v;
|
||||
weights[o] = w;
|
||||
indices[o] = o;
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
sortArray(
|
||||
indices,
|
||||
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
|
||||
);
|
||||
|
||||
let cweight = 0,
|
||||
cutoffIndex = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
cweight += weights[indices[i]];
|
||||
|
||||
if (cweight / weightSum >= cumulativeThreshold) {
|
||||
cutoffIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let positive = Number.POSITIVE_INFINITY,
|
||||
negative = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < cutoffIndex; i++) {
|
||||
const v = values[indices[i]];
|
||||
if (v > 0) {
|
||||
if (v < positive) positive = v;
|
||||
} else if (v < 0) {
|
||||
if (v > negative) negative = v;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
|
||||
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
|
||||
};
|
||||
}
|
||||
@@ -4,46 +4,93 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
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 quad_vert from '../../../mol-gl/shader/quad.vert';
|
||||
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
|
||||
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { arrayMin } from '../../../mol-util/array';
|
||||
import { isLittleEndian } from '../../../mol-util/is-little-endian';
|
||||
import { CollocationParams } from '../collocation';
|
||||
import { normalizeBasicOrder } from '../orbitals';
|
||||
import shader_frag from './shader.frag';
|
||||
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
|
||||
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
|
||||
import { MAIN, UTILS } from './shader.frag';
|
||||
|
||||
const AlphaOrbitalsSchema = {
|
||||
...QuadSchema,
|
||||
uDimensions: UniformSpec('v3'),
|
||||
uMin: UniformSpec('v3'),
|
||||
uDelta: UniformSpec('v3'),
|
||||
const Schema = {
|
||||
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
|
||||
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
|
||||
uWidth: UniformSpec('f'),
|
||||
uNCenters: UniformSpec('i'),
|
||||
uNAlpha: UniformSpec('i'),
|
||||
uNCoeff: UniformSpec('i'),
|
||||
uMaxCoeffs: UniformSpec('i'),
|
||||
uLittleEndian: UniformSpec('b')
|
||||
};
|
||||
const AlphaOrbitalsName = 'alpha-orbitals';
|
||||
const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag);
|
||||
type AlphaOrbitalsRenderable = ComputeRenderable<Values<typeof AlphaOrbitalsSchema>>
|
||||
|
||||
function createTextureData({
|
||||
basis,
|
||||
sphericalOrder,
|
||||
alphaOrbitals,
|
||||
cutoffThreshold
|
||||
}: CollocationParams) {
|
||||
const Orbitals = createGrid3dComputeRenderable({
|
||||
schema: Schema,
|
||||
loopBounds: ['uNCenters', 'uMaxCoeffs'],
|
||||
mainCode: MAIN,
|
||||
utilCode: UTILS,
|
||||
returnCode: 'v',
|
||||
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
|
||||
return createTextureData(params.grid, params.orbital);
|
||||
}
|
||||
});
|
||||
|
||||
const Density = createGrid3dComputeRenderable({
|
||||
schema: {
|
||||
...Schema,
|
||||
uOccupancy: UniformSpec('f'),
|
||||
},
|
||||
loopBounds: ['uNCenters', 'uMaxCoeffs'],
|
||||
mainCode: MAIN,
|
||||
utilCode: UTILS,
|
||||
returnCode: 'current + uOccupancy * v * v',
|
||||
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
|
||||
return {
|
||||
...createTextureData(params.grid, params.orbitals[0]),
|
||||
uOccupancy: 0
|
||||
};
|
||||
},
|
||||
cumulative: {
|
||||
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
|
||||
return params.orbitals.filter(o => o.occupancy !== 0);
|
||||
},
|
||||
update({ grid }, state: AlphaOrbital, values) {
|
||||
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
|
||||
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
|
||||
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
|
||||
return Orbitals(ctx, webgl, grid, { grid, orbital });
|
||||
}
|
||||
|
||||
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
|
||||
return Density(ctx, webgl, grid, { grid, orbitals });
|
||||
}
|
||||
|
||||
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
|
||||
const alpha = new Float32Array(alphaOrbitals.length);
|
||||
|
||||
let aO = 0;
|
||||
for (const atom of basis.atoms) {
|
||||
for (const shell of atom.shells) {
|
||||
for (const L of shell.angularMomentum) {
|
||||
const a0 = normalizeBasicOrder(L, alphaOrbitals.slice(aO, aO + 2 * L + 1), sphericalOrder);
|
||||
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
|
||||
aO += 2 * L + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
|
||||
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
|
||||
|
||||
let centerCount = 0;
|
||||
let baseCount = 0;
|
||||
let coeffCount = 0;
|
||||
@@ -75,7 +122,7 @@ function createTextureData({
|
||||
|
||||
let amIndex = 0;
|
||||
for (const L of shell.angularMomentum) {
|
||||
const a0 = normalizeBasicOrder(L, alphaOrbitals.slice(aO, aO + 2 * L + 1), sphericalOrder);
|
||||
const a0 = normalizeBasicOrder(L, orbital.alpha.slice(aO, aO + 2 * L + 1), sphericalOrder);
|
||||
|
||||
const cutoffRadius = cutoffThreshold > 0
|
||||
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(shell.exponents))
|
||||
@@ -110,105 +157,14 @@ function createTextureData({
|
||||
}
|
||||
}
|
||||
|
||||
return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff };
|
||||
}
|
||||
|
||||
function createAlphaOrbitalsRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable {
|
||||
const data = createTextureData(params);
|
||||
|
||||
const [nx, ny, nz] = params.grid.dimensions;
|
||||
const width = Math.ceil(Math.sqrt(nx * ny * nz));
|
||||
|
||||
const values: Values<typeof AlphaOrbitalsSchema> = {
|
||||
...QuadValues,
|
||||
uDimensions: ValueCell.create(params.grid.dimensions),
|
||||
uMin: ValueCell.create(params.grid.box.min),
|
||||
uDelta: ValueCell.create(params.grid.delta),
|
||||
uWidth: ValueCell.create(width),
|
||||
uNCenters: ValueCell.create(data.nCenters),
|
||||
uNAlpha: ValueCell.create(data.nAlpha),
|
||||
uNCoeff: ValueCell.create(data.nCoeff),
|
||||
uMaxCoeffs: ValueCell.create(data.maxCoeffs),
|
||||
tCenters: ValueCell.create({ width: data.nCenters, height: 1, array: data.centers }),
|
||||
tInfo: ValueCell.create({ width: data.nCenters, height: 1, array: data.info }),
|
||||
tCoeff: ValueCell.create({ width: data.nCoeff, height: 1, array: data.coeff }),
|
||||
tAlpha: ValueCell.create({ width: data.nAlpha, height: 1, array: data.alpha }),
|
||||
uLittleEndian: ValueCell.create(isLittleEndian()),
|
||||
return {
|
||||
uNCenters: centerCount,
|
||||
uNAlpha: baseCount,
|
||||
uNCoeff: coeffCount,
|
||||
uMaxCoeffs: maxCoeffs,
|
||||
tCenters: { width: centerCount, height: 1, array: centers },
|
||||
tInfo: { width: centerCount, height: 1, array: info },
|
||||
tCoeff: { width: coeffCount, height: 1, array: coeff },
|
||||
tAlpha: { width: baseCount, height: 1, array: alpha },
|
||||
};
|
||||
|
||||
const schema = { ...AlphaOrbitalsSchema };
|
||||
if (!ctx.isWebGL2) {
|
||||
// workaround for webgl1 limitation that loop counters need to be `const`
|
||||
(schema.uNCenters as any) = DefineSpec('number');
|
||||
(schema.uMaxCoeffs as any) = DefineSpec('number');
|
||||
}
|
||||
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
function getAlphaOrbitalsRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable {
|
||||
if (ctx.namedComputeRenderables[AlphaOrbitalsName]) {
|
||||
const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values;
|
||||
|
||||
const data = createTextureData(params);
|
||||
|
||||
const [nx, ny, nz] = params.grid.dimensions;
|
||||
const width = Math.ceil(Math.sqrt(nx * ny * nz));
|
||||
|
||||
ValueCell.update(v.uDimensions, params.grid.dimensions);
|
||||
ValueCell.update(v.uMin, params.grid.box.min);
|
||||
ValueCell.update(v.uDelta, params.grid.delta);
|
||||
ValueCell.updateIfChanged(v.uWidth, width);
|
||||
ValueCell.updateIfChanged(v.uNCenters, data.nCenters);
|
||||
ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha);
|
||||
ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff);
|
||||
ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs);
|
||||
ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers });
|
||||
ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info });
|
||||
ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff });
|
||||
ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha });
|
||||
ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian());
|
||||
|
||||
ctx.namedComputeRenderables[AlphaOrbitalsName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, params);
|
||||
}
|
||||
return ctx.namedComputeRenderables[AlphaOrbitalsName];
|
||||
}
|
||||
|
||||
export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, params: CollocationParams) {
|
||||
const [nx, ny, nz] = params.grid.dimensions;
|
||||
const renderable = getAlphaOrbitalsRenderable(webgl, params);
|
||||
const width = renderable.values.uWidth.ref.value;
|
||||
|
||||
if (!webgl.namedFramebuffers[AlphaOrbitalsName]) {
|
||||
webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
|
||||
|
||||
if (!webgl.namedTextures[AlphaOrbitalsName]) {
|
||||
webgl.namedTextures[AlphaOrbitalsName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
webgl.namedTextures[AlphaOrbitalsName].define(width, width);
|
||||
webgl.namedTextures[AlphaOrbitalsName].attachFramebuffer(framebuffer, 'color0');
|
||||
|
||||
const { gl, state } = webgl;
|
||||
framebuffer.bind();
|
||||
gl.viewport(0, 0, width, width);
|
||||
gl.scissor(0, 0, width, width);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
renderable.render();
|
||||
|
||||
const array = new Uint8Array(width * width * 4);
|
||||
webgl.readPixels(0, 0, width, width, array);
|
||||
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
|
||||
}
|
||||
|
||||
export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) {
|
||||
return !!webgl?.extensions.textureFloat;
|
||||
}
|
||||
@@ -4,69 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform vec2 uQuadShift;
|
||||
uniform vec3 uDimensions;
|
||||
uniform vec3 uMin;
|
||||
uniform vec3 uDelta;
|
||||
|
||||
uniform sampler2D tCenters;
|
||||
uniform sampler2D tInfo;
|
||||
uniform sampler2D tCoeff;
|
||||
uniform sampler2D tAlpha;
|
||||
|
||||
uniform float uWidth;
|
||||
|
||||
#ifndef uNCenters
|
||||
uniform int uNCenters;
|
||||
#endif
|
||||
|
||||
uniform int uNCoeff;
|
||||
uniform int uNAlpha;
|
||||
|
||||
uniform bool uLittleEndian;
|
||||
|
||||
float shiftRight (float v, float amt) {
|
||||
v = floor(v) + 0.5;
|
||||
return floor(v / exp2(amt));
|
||||
}
|
||||
float shiftLeft (float v, float amt) {
|
||||
return floor(v * exp2(amt) + 0.5);
|
||||
}
|
||||
float maskLast (float v, float bits) {
|
||||
return mod(v, shiftLeft(1.0, bits));
|
||||
}
|
||||
float extractBits (float num, float from, float to) {
|
||||
from = floor(from + 0.5); to = floor(to + 0.5);
|
||||
return maskLast(shiftRight(num, from), to - from);
|
||||
}
|
||||
// Adapted from https://github.com/equinor/glsl-float-to-rgba
|
||||
// MIT License, Copyright (c) 2020 Equinor
|
||||
vec4 floatToRgba(float texelFloat) {
|
||||
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
|
||||
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
|
||||
texelFloat = abs(texelFloat);
|
||||
float exponent = floor(log2(texelFloat));
|
||||
float biased_exponent = exponent + 127.0;
|
||||
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
|
||||
float t = biased_exponent / 2.0;
|
||||
float last_bit_of_biased_exponent = fract(t) * 2.0;
|
||||
float remaining_bits_of_biased_exponent = floor(t);
|
||||
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
|
||||
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
|
||||
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
|
||||
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
|
||||
return (
|
||||
uLittleEndian
|
||||
? vec4(byte4, byte3, byte2, byte1)
|
||||
: vec4(byte1, byte2, byte3, byte4)
|
||||
);
|
||||
}
|
||||
|
||||
export const UTILS = `
|
||||
float L1(vec3 p, float a0, float a1, float a2) {
|
||||
return a0 * p.z + a1 * p.x + a2 * p.y;
|
||||
}
|
||||
@@ -117,12 +55,10 @@ float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, flo
|
||||
}
|
||||
|
||||
float alpha(float offset, float f) {
|
||||
#ifdef uMaxCoeffs
|
||||
#ifdef WEBGL1
|
||||
// in webgl1, the value is in the alpha channel!
|
||||
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
|
||||
#endif
|
||||
|
||||
#ifndef uMaxCoeffs
|
||||
#else
|
||||
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
|
||||
#endif
|
||||
}
|
||||
@@ -153,7 +89,7 @@ float Y(int L, vec3 X, float aO, float fA) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#ifndef uMaxCoeffs
|
||||
#ifndef WEBGL1
|
||||
float R(float R2, int start, int end, float fCoeff) {
|
||||
float gauss = 0.0;
|
||||
for (int i = start; i < end; i++) {
|
||||
@@ -162,9 +98,7 @@ float Y(int L, vec3 X, float aO, float fA) {
|
||||
}
|
||||
return gauss;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef uMaxCoeffs
|
||||
#else
|
||||
float R(float R2, int start, int end, float fCoeff) {
|
||||
float gauss = 0.0;
|
||||
int o = start;
|
||||
@@ -178,28 +112,13 @@ float Y(int L, vec3 X, float aO, float fA) {
|
||||
return gauss;
|
||||
}
|
||||
#endif
|
||||
`;
|
||||
|
||||
float intDiv(float a, float b) { return float(int(a) / int(b)); }
|
||||
float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
|
||||
|
||||
void main(void) {
|
||||
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
|
||||
|
||||
// axis order fast to slow Z, Y, X
|
||||
// TODO: support arbitrary axis orders?
|
||||
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
|
||||
float j = intMod(kk, uDimensions.y);
|
||||
float i = intDiv(kk, uDimensions.y);
|
||||
|
||||
vec3 xyz = uMin + uDelta * vec3(i, j, k);
|
||||
|
||||
export const MAIN = `
|
||||
float fCenter = 1.0 / float(uNCenters - 1);
|
||||
float fCoeff = 1.0 / float(uNCoeff - 1);
|
||||
float fA = 1.0 / float(uNAlpha - 1);
|
||||
|
||||
// gl_FragColor = floatToRgba(offset);
|
||||
// return;
|
||||
|
||||
float v = 0.0;
|
||||
|
||||
for (int i = 0; i < uNCenters; i++) {
|
||||
@@ -223,8 +142,4 @@ void main(void) {
|
||||
|
||||
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
|
||||
}
|
||||
|
||||
// TODO: render to single component float32 texture in WebGL2
|
||||
gl_FragColor = floatToRgba(v);
|
||||
}
|
||||
`;
|
||||
@@ -6,88 +6,126 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// gaussian:
|
||||
// R_0, R^+_1, R^-_1, ..., R^+_l, R^-_l
|
||||
// cca:
|
||||
// R^-_(l), R^-_(l-1), ..., R_0, ..., R^+_(l-1), R^+_l
|
||||
// cca-reverse:
|
||||
// R^+_(l), R^+_(l-1), ..., R_0, ..., R^-_(l-1), R^-_l
|
||||
export type SphericalBasisOrder = 'gaussian' | 'cca' | 'cca-reverse';
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { sphericalCollocation } from './collocation';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
|
||||
|
||||
export function normalizeBasicOrder(
|
||||
L: number,
|
||||
alpha: number[],
|
||||
order: SphericalBasisOrder
|
||||
) {
|
||||
if (order === 'gaussian' || L === 0) return alpha;
|
||||
// setDebugMode(true);
|
||||
|
||||
const ret: number[] = [alpha[L]];
|
||||
for (let l = 0; l < L; l++) {
|
||||
const a = alpha[L - l - 1],
|
||||
b = alpha[L + l + 1];
|
||||
if (order === 'cca') ret.push(b, a);
|
||||
else ret.push(a, b);
|
||||
export function createSphericalCollocationGrid(
|
||||
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
|
||||
): Task<CubeGrid> {
|
||||
return Task.create('Spherical Collocation Grid', async (ctx) => {
|
||||
const cubeGrid = initCubeGrid(params);
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
// console.time('cpu');
|
||||
matrix = await sphericalCollocation(cubeGrid, orbital, ctx);
|
||||
// console.timeEnd('cpu');
|
||||
}
|
||||
|
||||
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
|
||||
let isovalues: { negative?: number, positive?: number } | undefined;
|
||||
|
||||
if (!params.doNotComputeIsovalues) {
|
||||
isovalues = computeOrbitalIsocontourValues(matrix, 0.85);
|
||||
}
|
||||
|
||||
return { grid, isovalues };
|
||||
});
|
||||
}
|
||||
|
||||
export function computeOrbitalIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
|
||||
let weightSum = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
weightSum += w;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
const avgWeight = weightSum / input.length;
|
||||
let minWeight = 3 * avgWeight;
|
||||
|
||||
export type SphericalFunc = (
|
||||
alpha: number[],
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
) => number;
|
||||
// do not try to identify isovalues for degenerate data
|
||||
// e.g. all values are almost same
|
||||
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
|
||||
return { negative: void 0, positive: void 0 };
|
||||
}
|
||||
|
||||
export const SphericalFunctions: SphericalFunc[] = [L0, L1, L2, L3, L4];
|
||||
let size = 0;
|
||||
while (true) {
|
||||
let csum = 0;
|
||||
size = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
if (w >= minWeight) {
|
||||
csum += w;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
// L_i functions were auto-generated.
|
||||
if (csum / weightSum > cumulativeThreshold) {
|
||||
break;
|
||||
}
|
||||
|
||||
function L0(alpha: number[], x: number, y: number, z: number) {
|
||||
return alpha[0];
|
||||
}
|
||||
minWeight -= avgWeight;
|
||||
}
|
||||
|
||||
function L1(alpha: number[], x: number, y: number, z: number) {
|
||||
return alpha[0] * z + alpha[1] * x + alpha[2] * y;
|
||||
}
|
||||
const values = new Float32Array(size);
|
||||
const weights = new Float32Array(size);
|
||||
const indices = new Int32Array(size);
|
||||
|
||||
function L2(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
return (
|
||||
alpha[0] * (-0.5 * xx - 0.5 * yy + zz) +
|
||||
alpha[1] * (1.7320508075688772 * x * z) +
|
||||
alpha[2] * (1.7320508075688772 * y * z) +
|
||||
alpha[3] * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
|
||||
alpha[4] * (1.7320508075688772 * x * y)
|
||||
let o = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
if (w >= minWeight) {
|
||||
values[o] = v;
|
||||
weights[o] = w;
|
||||
indices[o] = o;
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
sortArray(
|
||||
indices,
|
||||
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
|
||||
);
|
||||
}
|
||||
|
||||
function L3(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
return (
|
||||
alpha[0] * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
|
||||
alpha[1] * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
|
||||
alpha[2] * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
|
||||
alpha[3] * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
|
||||
alpha[4] * (3.872983346207417 * x * y * z) +
|
||||
alpha[5] * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
|
||||
alpha[6] * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
|
||||
);
|
||||
}
|
||||
let cweight = 0,
|
||||
cutoffIndex = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
cweight += weights[indices[i]];
|
||||
|
||||
function L4(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
const xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
|
||||
return (
|
||||
alpha[0] * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
|
||||
alpha[1] * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
|
||||
alpha[2] * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
|
||||
alpha[3] * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
|
||||
alpha[4] * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
|
||||
alpha[5] * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
|
||||
alpha[6] * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
|
||||
alpha[7] * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
|
||||
alpha[8] * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
|
||||
);
|
||||
}
|
||||
if (cweight / weightSum >= cumulativeThreshold) {
|
||||
cutoffIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let positive = Number.POSITIVE_INFINITY,
|
||||
negative = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < cutoffIndex; i++) {
|
||||
const v = values[indices[i]];
|
||||
if (v > 0) {
|
||||
if (v < positive) positive = v;
|
||||
} else if (v < 0) {
|
||||
if (v > negative) negative = v;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
|
||||
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
|
||||
};
|
||||
}
|
||||
93
src/extensions/alpha-orbitals/spherical-functions.ts
Normal file
93
src/extensions/alpha-orbitals/spherical-functions.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Inspired by https://github.com/dgasmith/gau2grid.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// gaussian:
|
||||
// R_0, R^+_1, R^-_1, ..., R^+_l, R^-_l
|
||||
// cca:
|
||||
// R^-_(l), R^-_(l-1), ..., R_0, ..., R^+_(l-1), R^+_l
|
||||
// cca-reverse:
|
||||
// R^+_(l), R^+_(l-1), ..., R_0, ..., R^-_(l-1), R^-_l
|
||||
export type SphericalBasisOrder = 'gaussian' | 'cca' | 'cca-reverse';
|
||||
|
||||
export function normalizeBasicOrder(
|
||||
L: number,
|
||||
alpha: number[],
|
||||
order: SphericalBasisOrder
|
||||
) {
|
||||
if (order === 'gaussian' || L === 0) return alpha;
|
||||
|
||||
const ret: number[] = [alpha[L]];
|
||||
for (let l = 0; l < L; l++) {
|
||||
const a = alpha[L - l - 1],
|
||||
b = alpha[L + l + 1];
|
||||
if (order === 'cca') ret.push(b, a);
|
||||
else ret.push(a, b);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export type SphericalFunc = (
|
||||
alpha: number[],
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
) => number;
|
||||
|
||||
export const SphericalFunctions: SphericalFunc[] = [L0, L1, L2, L3, L4];
|
||||
|
||||
// L_i functions were auto-generated.
|
||||
|
||||
function L0(alpha: number[], x: number, y: number, z: number) {
|
||||
return alpha[0];
|
||||
}
|
||||
|
||||
function L1(alpha: number[], x: number, y: number, z: number) {
|
||||
return alpha[0] * z + alpha[1] * x + alpha[2] * y;
|
||||
}
|
||||
|
||||
function L2(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
return (
|
||||
alpha[0] * (-0.5 * xx - 0.5 * yy + zz) +
|
||||
alpha[1] * (1.7320508075688772 * x * z) +
|
||||
alpha[2] * (1.7320508075688772 * y * z) +
|
||||
alpha[3] * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
|
||||
alpha[4] * (1.7320508075688772 * x * y)
|
||||
);
|
||||
}
|
||||
|
||||
function L3(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
return (
|
||||
alpha[0] * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
|
||||
alpha[1] * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
|
||||
alpha[2] * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
|
||||
alpha[3] * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
|
||||
alpha[4] * (3.872983346207417 * x * y * z) +
|
||||
alpha[5] * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
|
||||
alpha[6] * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
|
||||
);
|
||||
}
|
||||
|
||||
function L4(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
const xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
|
||||
return (
|
||||
alpha[0] * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
|
||||
alpha[1] * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
|
||||
alpha[2] * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
|
||||
alpha[3] * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
|
||||
alpha[4] * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
|
||||
alpha[5] * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
|
||||
alpha[6] * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
|
||||
alpha[7] * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
|
||||
alpha[8] * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
|
||||
);
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
*/
|
||||
|
||||
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { Basis, createSphericalCollocationGrid, CubeGrid } from './cubes';
|
||||
import { createSphericalCollocationGrid } from './orbitals';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
import { SphericalBasisOrder } from './orbitals';
|
||||
import { SphericalBasisOrder } from './spherical-functions';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
@@ -17,8 +17,10 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
|
||||
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
|
||||
import { createSphericalCollocationDensityGrid } from './density';
|
||||
|
||||
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: { energy: number, alpha: number[] }[] }>({ name: 'Basis', typeClass: 'Object' }) { }
|
||||
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
|
||||
|
||||
export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
|
||||
name: 'static-basis-and-orbitals',
|
||||
@@ -29,7 +31,7 @@ export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
|
||||
label: PD.Text('Orbital Data', { isHidden: true }),
|
||||
basis: PD.Value<Basis>(void 0 as any, { isHidden: true }),
|
||||
order: PD.Text<SphericalBasisOrder>('gaussian' as SphericalBasisOrder, { isHidden: true }),
|
||||
orbitals: PD.Value<{ energy: number, alpha: number[] }[]>([], { isHidden: true })
|
||||
orbitals: PD.Value<AlphaOrbital[]>([], { isHidden: true })
|
||||
},
|
||||
})({
|
||||
apply({ params }) {
|
||||
@@ -38,7 +40,6 @@ export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
|
||||
const CreateOrbitalVolumeParamBase = {
|
||||
forceCpuCompute: PD.Boolean(false),
|
||||
cutoffThreshold: PD.Numeric(0.0015, { min: 0, max: 0.1, step: 0.0001 }),
|
||||
boxExpand: PD.Numeric(4.5, { min: 0, max: 7, step: 0.1 }),
|
||||
gridSpacing: PD.ObjectList({ atomCount: PD.Numeric(0), spacing: PD.Numeric(0.35, { min: 0.1, max: 2, step: 0.01 }) }, e => `Atoms ${e.atomCount}: ${e.spacing}`, {
|
||||
@@ -72,11 +73,38 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
|
||||
const data = await createSphericalCollocationGrid({
|
||||
basis: a.data.basis,
|
||||
cutoffThreshold: params.cutoffThreshold,
|
||||
alphaOrbitals: a.data.orbitals[params.index].alpha,
|
||||
sphericalOrder: a.data.order,
|
||||
boxExpand: params.boxExpand,
|
||||
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
|
||||
}, params.forceCpuCompute ? void 0 : plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
const volume: Volume = {
|
||||
grid: data.grid,
|
||||
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
|
||||
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
|
||||
name: 'create-orbital-density-volume',
|
||||
display: 'Orbital Density Volume',
|
||||
from: BasisAndOrbitals,
|
||||
to: PluginStateObject.Volume.Data,
|
||||
params: CreateOrbitalVolumeParamBase
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Orbital Volume', async ctx => {
|
||||
const data = await createSphericalCollocationDensityGrid({
|
||||
basis: a.data.basis,
|
||||
cutoffThreshold: params.cutoffThreshold,
|
||||
sphericalOrder: a.data.order,
|
||||
boxExpand: params.boxExpand,
|
||||
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
|
||||
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
const volume: Volume = {
|
||||
grid: data.grid,
|
||||
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
|
||||
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
|
||||
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
|
||||
import { MembraneOrientation } from './prop';
|
||||
import { ThemeRegistryContext } from '../../mol-theme/theme';
|
||||
@@ -27,6 +26,7 @@ import { MembraneOrientationProvider } from './prop';
|
||||
import { MarkerActions } from '../../mol-util/marker-action';
|
||||
import { lociLabel } from '../../mol-theme/label';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
|
||||
const SharedParams = {
|
||||
color: PD.Color(ColorNames.lightgrey),
|
||||
@@ -61,7 +61,6 @@ export type BilayerRimsParams = typeof BilayerRimsParams
|
||||
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
|
||||
|
||||
const MembraneOrientationVisuals = {
|
||||
'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
|
||||
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
|
||||
};
|
||||
@@ -91,9 +90,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation
|
||||
factory: MembraneOrientationRepresentation,
|
||||
getParams: getMembraneOrientationParams,
|
||||
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
|
||||
defaultColorTheme: { name: 'uniform' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0
|
||||
defaultColorTheme: { name: 'shape-group' },
|
||||
defaultSizeTheme: { name: 'shape-group' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => MembraneOrientationProvider.ref(data, false)
|
||||
}
|
||||
});
|
||||
|
||||
function membraneLabel(data: Structure) {
|
||||
@@ -151,28 +154,3 @@ function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal
|
||||
MeshBuilder.addPrimitive(state, Mat4.id, circle);
|
||||
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
|
||||
}
|
||||
|
||||
function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> {
|
||||
const { density } = props;
|
||||
const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!;
|
||||
const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius);
|
||||
|
||||
const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry);
|
||||
getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius);
|
||||
getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius);
|
||||
return Shape.create('Bilayer spheres', data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data));
|
||||
}
|
||||
|
||||
function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) {
|
||||
Vec3.normalize(normalVector, normalVector);
|
||||
const d = -Vec3.dot(normalVector, point);
|
||||
const rep = Vec3();
|
||||
for (let i = -1000, il = 1000; i < il; i += density) {
|
||||
for (let j = -1000, jl = 1000; j < jl; j += density) {
|
||||
Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]);
|
||||
if (Vec3.squaredDistance(rep, point) < sqRadius) {
|
||||
spheresBuilder.add(rep[0], rep[1], rep[2], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ const ColorLegend = TableLegend([
|
||||
]);
|
||||
|
||||
export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) {
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value;
|
||||
const validationReport = !!ctx.structure && ctx.structure.models.length > 0 && ValidationReportProvider.get(ctx.structure.models[0]).value;
|
||||
const options: [string, string][] = [];
|
||||
if (validationReport) {
|
||||
const kinds = new Set<string>();
|
||||
@@ -48,7 +48,7 @@ export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQua
|
||||
export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const validationReport = !!ctx.structure && ctx.structure.models.length > 0 ? ValidationReportProvider.get(ctx.structure.models[0]) : void 0;
|
||||
const contextHash = validationReport?.version;
|
||||
|
||||
const value = validationReport?.value;
|
||||
|
||||
@@ -209,8 +209,8 @@ namespace Camera {
|
||||
up: Vec3.create(0, 1, 0),
|
||||
target: Vec3.create(0, 0, 0),
|
||||
|
||||
radius: 10,
|
||||
radiusMax: 10,
|
||||
radius: 0,
|
||||
radiusMax: 0,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
};
|
||||
|
||||
@@ -149,10 +149,11 @@ namespace Canvas3D {
|
||||
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale?: number, pickScale: number, enableWboit: boolean }> = {}) {
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number, enableWboit: boolean }> = {}) {
|
||||
const antialias = (attribs.antialias ?? true) && !attribs.enableWboit;
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: true,
|
||||
antialias: attribs.antialias ?? true,
|
||||
antialias,
|
||||
depth: true,
|
||||
preserveDrawingBuffer: true,
|
||||
premultipliedAlpha: true,
|
||||
@@ -197,6 +198,14 @@ namespace Canvas3D {
|
||||
if (isDebugMode) console.log('context restored');
|
||||
}, false);
|
||||
|
||||
// disable postprocessing anti-aliasing if canvas anti-aliasing is enabled
|
||||
if (antialias && !props.postprocessing?.antialiasing) {
|
||||
props.postprocessing = {
|
||||
...DefaultCanvas3DParams.postprocessing,
|
||||
antialiasing: { name: 'off', params: {} }
|
||||
};
|
||||
}
|
||||
|
||||
return create(webgl, input, passes, props, { pixelScale });
|
||||
}
|
||||
|
||||
@@ -420,7 +429,8 @@ namespace Canvas3D {
|
||||
const b = r.values.boundingSphere.ref.value;
|
||||
if (!b.radius) continue;
|
||||
|
||||
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
|
||||
const cameraDist = Vec3.distance(cameraSphere.center, b.center);
|
||||
if ((cameraDist > cameraSphere.radius || cameraDist > b.radius || b.radius > camera.state.radiusMax) && !Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
|
||||
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export class Canvas3dInteractionHelper {
|
||||
|
||||
private leave() {
|
||||
this.inside = false;
|
||||
if (Representation.Loci.isEmpty(this.prevLoci)) {
|
||||
if (!Representation.Loci.isEmpty(this.prevLoci)) {
|
||||
this.prevLoci = Representation.Loci.Empty;
|
||||
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, button: this.button, modifiers: this.modifiers });
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export class DrawPass {
|
||||
|
||||
this.drawTarget = createNullRenderTarget(webgl.gl);
|
||||
|
||||
this.colorTarget = webgl.createRenderTarget(width, height);
|
||||
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
|
||||
this.packedDepth = !extensions.depthTexture;
|
||||
|
||||
this.depthTarget = webgl.createRenderTarget(width, height);
|
||||
@@ -195,7 +195,7 @@ export class DrawPass {
|
||||
if (!toDrawingBuffer) {
|
||||
if (!this.packedDepth) {
|
||||
this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
renderer.clearDepth();
|
||||
renderer.clearDepth(); // from previous frame
|
||||
}
|
||||
renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives);
|
||||
|
||||
@@ -206,6 +206,10 @@ export class DrawPass {
|
||||
renderer.renderDepth(scene.volumes, camera, this.depthTexturePrimitives);
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
|
||||
if (!this.packedDepth) {
|
||||
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
}
|
||||
}
|
||||
|
||||
renderer.renderBlendedTransparent(scene.primitives, camera, null);
|
||||
|
||||
@@ -17,9 +17,9 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { DrawPass } from './draw';
|
||||
import { Camera, ICamera } from '../../mol-canvas3d/camera';
|
||||
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert';
|
||||
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag';
|
||||
import fxaa_frag from '../../mol-gl/shader/fxaa.frag';
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
|
||||
const PostprocessingSchema = {
|
||||
@@ -48,11 +48,14 @@ const PostprocessingShaderCode = ShaderCode('postprocessing', quad_vert, postpro
|
||||
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
|
||||
|
||||
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture): PostprocessingRenderable {
|
||||
const width = colorTexture.getWidth();
|
||||
const height = colorTexture.getHeight();
|
||||
|
||||
const values: Values<typeof PostprocessingSchema> = {
|
||||
...QuadValues,
|
||||
tColor: ValueCell.create(colorTexture),
|
||||
tPackedDepth: ValueCell.create(depthTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
|
||||
uTexSize: ValueCell.create(Vec2.create(width, height)),
|
||||
|
||||
dOrthographic: ValueCell.create(0),
|
||||
uNear: ValueCell.create(1),
|
||||
@@ -92,30 +95,39 @@ export const PostprocessingParams = {
|
||||
threshold: PD.Numeric(0.8, { min: 0, max: 5, step: 0.01 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Draw outline around 3D objects' })
|
||||
}, { cycle: true, description: 'Draw outline around 3D objects' }),
|
||||
antialiasing: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
edgeThresholdMin:PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }),
|
||||
edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }),
|
||||
iterations: PD.Numeric(12, { min: 0, max: 32, step: 1 }, { description: 'Number of edge exploration steps.' }),
|
||||
subpixelQuality: PD.Numeric(1.00, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Fast Approximate Anti-Aliasing (FXAA)' }),
|
||||
};
|
||||
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
|
||||
|
||||
export class PostprocessingPass {
|
||||
static isEnabled(props: PostprocessingProps) {
|
||||
return props.occlusion.name === 'on' || props.outline.name === 'on';
|
||||
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'on';
|
||||
}
|
||||
|
||||
target: RenderTarget
|
||||
renderable: PostprocessingRenderable
|
||||
readonly target: RenderTarget
|
||||
|
||||
private readonly tmpTarget: RenderTarget
|
||||
private readonly renderable: PostprocessingRenderable
|
||||
private readonly fxaa: FxaaRenderable
|
||||
|
||||
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
|
||||
this.target = webgl.createRenderTarget(drawPass.colorTarget.getWidth(), drawPass.colorTarget.getHeight(), false);
|
||||
const { colorTarget, depthTexture } = drawPass;
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture);
|
||||
}
|
||||
const width = colorTarget.getWidth();
|
||||
const height = colorTarget.getHeight();
|
||||
|
||||
private bindTarget(toDrawingBuffer: boolean) {
|
||||
if (toDrawingBuffer) {
|
||||
this.webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.target.bind();
|
||||
}
|
||||
this.target = webgl.createRenderTarget(width, height, false);
|
||||
this.tmpTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture);
|
||||
this.fxaa = getFxaaRenderable(webgl, this.tmpTarget.texture);
|
||||
}
|
||||
|
||||
syncSize() {
|
||||
@@ -125,11 +137,29 @@ export class PostprocessingPass {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
if (width !== w || height !== h) {
|
||||
this.target.setSize(width, height);
|
||||
this.tmpTarget.setSize(width, height);
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.fxaa.values.uTexSizeInv, Vec2.set(this.fxaa.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
|
||||
}
|
||||
}
|
||||
|
||||
_render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
private updateState(camera: ICamera) {
|
||||
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 } = camera.viewport;
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
|
||||
state.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private _renderPostprocessing(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
const { values } = this.renderable;
|
||||
|
||||
ValueCell.updateIfChanged(values.uFar, camera.far);
|
||||
@@ -166,21 +196,66 @@ export class PostprocessingPass {
|
||||
this.renderable.update();
|
||||
}
|
||||
|
||||
const { gl, state } = this.webgl;
|
||||
this.bindTarget(toDrawingBuffer);
|
||||
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
if (props.antialiasing.name === 'on') {
|
||||
this.tmpTarget.bind();
|
||||
} else if (toDrawingBuffer) {
|
||||
this.webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.target.bind();
|
||||
}
|
||||
|
||||
this.updateState(camera);
|
||||
this.renderable.render();
|
||||
}
|
||||
|
||||
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
if (props.antialiasing.name === 'off') return;
|
||||
|
||||
const { values } = this.fxaa;
|
||||
|
||||
let needsUpdate = false;
|
||||
|
||||
const input = (props.occlusion.name === 'on' || props.outline.name === 'on')
|
||||
? this.tmpTarget.texture : this.drawPass.colorTarget.texture;
|
||||
if (values.tColor.ref.value !== input) {
|
||||
ValueCell.update(this.fxaa.values.tColor, input);
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props.antialiasing.params;
|
||||
if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true;
|
||||
ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin);
|
||||
if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true;
|
||||
ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax);
|
||||
if (values.dIterations.ref.value !== iterations) needsUpdate = true;
|
||||
ValueCell.updateIfChanged(values.dIterations, iterations);
|
||||
if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true;
|
||||
ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality);
|
||||
|
||||
if (needsUpdate) {
|
||||
this.fxaa.update();
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
this.webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.target.bind();
|
||||
}
|
||||
|
||||
this.updateState(camera);
|
||||
this.fxaa.render();
|
||||
}
|
||||
|
||||
private _render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
if (props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'off') {
|
||||
this._renderPostprocessing(camera, toDrawingBuffer, props);
|
||||
}
|
||||
|
||||
if (props.antialiasing.name === 'on') {
|
||||
this._renderFxaa(camera, toDrawingBuffer, props);
|
||||
}
|
||||
}
|
||||
|
||||
render(camera: Camera | StereoCamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
if (StereoCamera.is(camera)) {
|
||||
this._render(camera.left, toDrawingBuffer, props);
|
||||
@@ -189,4 +264,40 @@ export class PostprocessingPass {
|
||||
this._render(camera, toDrawingBuffer, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const FxaaSchema = {
|
||||
...QuadSchema,
|
||||
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
uTexSizeInv: UniformSpec('v2'),
|
||||
|
||||
dEdgeThresholdMin: DefineSpec('number'),
|
||||
dEdgeThresholdMax: DefineSpec('number'),
|
||||
dIterations: DefineSpec('number'),
|
||||
dSubpixelQuality: DefineSpec('number'),
|
||||
};
|
||||
const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag);
|
||||
type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>>
|
||||
|
||||
function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable {
|
||||
const width = colorTexture.getWidth();
|
||||
const height = colorTexture.getHeight();
|
||||
|
||||
const values: Values<typeof FxaaSchema> = {
|
||||
...QuadValues,
|
||||
tColor: ValueCell.create(colorTexture),
|
||||
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
|
||||
|
||||
dEdgeThresholdMin: ValueCell.create(0.0312),
|
||||
dEdgeThresholdMax: ValueCell.create(0.125),
|
||||
dIterations: ValueCell.create(12),
|
||||
dSubpixelQuality: ValueCell.create(0.75),
|
||||
};
|
||||
|
||||
const schema = { ...FxaaSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
223
src/mol-gl/compute/grid3d.ts
Normal file
223
src/mol-gl/compute/grid3d.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common';
|
||||
import shader_template from '../shader/util/grid3d-template.frag';
|
||||
import quad_vert from '../shader/quad.vert';
|
||||
import { ShaderCode } from '../shader-code';
|
||||
import { UUID, ValueCell } from '../../mol-util';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform';
|
||||
import { QuadSchema, QuadValues } from './util';
|
||||
import { createComputeRenderItem } from '../webgl/render-item';
|
||||
import { createComputeRenderable } from '../renderable';
|
||||
import { isLittleEndian } from '../../mol-util/is-little-endian';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
export function canComputeGrid3dOnGPU(webgl?: WebGLContext) {
|
||||
return !!webgl?.extensions.textureFloat;
|
||||
}
|
||||
|
||||
export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> {
|
||||
schema: S,
|
||||
// indicate which params are loop bounds for WebGL1 compat
|
||||
loopBounds?: (keyof S)[]
|
||||
utilCode?: string,
|
||||
mainCode: string,
|
||||
returnCode: string,
|
||||
|
||||
values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
|
||||
|
||||
cumulative?: {
|
||||
states(params: P): CS[],
|
||||
update(params: P, state: CS, values: Values<S>): void,
|
||||
// call gl.readPixes every 'yieldPeriod' states to split the computation
|
||||
// into multiple parts, if not set, the computation will be synchronous
|
||||
yieldPeriod?: number
|
||||
}
|
||||
}
|
||||
|
||||
const FrameBufferName = 'grid3d-computable' as const;
|
||||
const Texture0Name = 'grid3d-computable-0' as const;
|
||||
const Texture1Name = 'grid3d-computable-1' as const;
|
||||
|
||||
const SchemaBase = {
|
||||
...QuadSchema,
|
||||
uDimensions: UniformSpec('v3'),
|
||||
uMin: UniformSpec('v3'),
|
||||
uDelta: UniformSpec('v3'),
|
||||
uWidth: UniformSpec('f'),
|
||||
uLittleEndian: UniformSpec('b'),
|
||||
};
|
||||
|
||||
const CumulativeSumSchema = {
|
||||
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
|
||||
};
|
||||
|
||||
export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
|
||||
const id = UUID.create22();
|
||||
|
||||
const uniforms: string[] = [];
|
||||
|
||||
objectForEach(spec.schema, (u, k) => {
|
||||
if (u.type === 'define') return;
|
||||
if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported');
|
||||
const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0;
|
||||
if (isBound) uniforms.push(`#ifndef ${k}`);
|
||||
if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`);
|
||||
else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`);
|
||||
if (isBound) uniforms.push(`#endif`);
|
||||
});
|
||||
|
||||
const code = shader_template
|
||||
.replace('{UNIFORMS}', uniforms.join('\n'))
|
||||
.replace('{UTILS}', spec.utilCode ?? '')
|
||||
.replace('{MAIN}', spec.mainCode)
|
||||
.replace('{RETURN}', spec.returnCode);
|
||||
|
||||
const shader = ShaderCode(id, quad_vert, code);
|
||||
|
||||
return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => {
|
||||
const schema: RenderableSchema = {
|
||||
...SchemaBase,
|
||||
...(spec.cumulative ? CumulativeSumSchema : {}),
|
||||
...spec.schema,
|
||||
};
|
||||
|
||||
if (!webgl.isWebGL2) {
|
||||
if (spec.loopBounds) {
|
||||
for (const b of spec.loopBounds) {
|
||||
(schema as any)[b] = DefineSpec('number');
|
||||
}
|
||||
}
|
||||
(schema as any)['WEBGL1'] = DefineSpec('boolean');
|
||||
}
|
||||
|
||||
if (spec.cumulative) {
|
||||
(schema as any)['CUMULATIVE'] = DefineSpec('boolean');
|
||||
}
|
||||
|
||||
if (!webgl.namedFramebuffers[FrameBufferName]) {
|
||||
webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[FrameBufferName];
|
||||
|
||||
if (!webgl.namedTextures[Texture0Name]) {
|
||||
webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
if (spec.cumulative && !webgl.namedTextures[Texture1Name]) {
|
||||
webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
|
||||
const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]];
|
||||
|
||||
const [nx, ny, nz] = grid.dimensions;
|
||||
const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
|
||||
|
||||
const values: UnboxedValues<S & typeof SchemaBase> = {
|
||||
uDimensions: grid.dimensions,
|
||||
uMin: grid.box.min,
|
||||
uDelta: getRegularGrid3dDelta(grid),
|
||||
uWidth,
|
||||
uLittleEndian: isLittleEndian(),
|
||||
...spec.values(params, grid)
|
||||
} as any;
|
||||
|
||||
if (!webgl.isWebGL2) {
|
||||
(values as any).WEBGL1 = true;
|
||||
}
|
||||
if (spec.cumulative) {
|
||||
(values as any).tCumulativeSum = tex[0];
|
||||
(values as any).CUMULATIVE = true;
|
||||
}
|
||||
|
||||
let renderable = webgl.namedComputeRenderables[id];
|
||||
let cells: RenderableValues;
|
||||
if (renderable) {
|
||||
cells = renderable.values as RenderableValues;
|
||||
objectForEach(values, (c, k) => {
|
||||
const s = schema[k];
|
||||
if (s?.type === 'value' || s?.type === 'attribute') return;
|
||||
|
||||
if (!s || !isUniformValueScalar(s.kind as any)) {
|
||||
ValueCell.update(cells[k], c);
|
||||
} else {
|
||||
ValueCell.updateIfChanged(cells[k], c);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cells = {} as any;
|
||||
objectForEach(QuadValues, (v, k) => (cells as any)[k] = v);
|
||||
objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v));
|
||||
renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells);
|
||||
}
|
||||
|
||||
const array = new Uint8Array(uWidth * uWidth * 4);
|
||||
if (spec.cumulative) {
|
||||
const { gl } = webgl;
|
||||
|
||||
const states = spec.cumulative.states(params);
|
||||
|
||||
tex[0].define(uWidth, uWidth);
|
||||
tex[1].define(uWidth, uWidth);
|
||||
|
||||
resetGl(webgl, uWidth);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
tex[0].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
tex[1].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
if (spec.cumulative.yieldPeriod) {
|
||||
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
|
||||
}
|
||||
|
||||
const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
|
||||
|
||||
for (let i = 0; i < states.length; i++) {
|
||||
ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
|
||||
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
|
||||
resetGl(webgl, uWidth);
|
||||
spec.cumulative.update(params, states[i], cells as any);
|
||||
renderable.update();
|
||||
renderable.render();
|
||||
|
||||
if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
|
||||
if (i % yieldPeriod === yieldPeriod - 1) {
|
||||
webgl.readPixels(0, 0, 1, 1, array);
|
||||
}
|
||||
if (ctx.shouldUpdate) {
|
||||
await ctx.update({ current: i + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tex[0].define(uWidth, uWidth);
|
||||
tex[0].attachFramebuffer(framebuffer, 'color0');
|
||||
framebuffer.bind();
|
||||
resetGl(webgl, uWidth);
|
||||
renderable.update();
|
||||
renderable.render();
|
||||
}
|
||||
|
||||
webgl.readPixels(0, 0, uWidth, uWidth, array);
|
||||
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
|
||||
};
|
||||
}
|
||||
|
||||
function resetGl(webgl: WebGLContext, w: number) {
|
||||
const { gl, state } = webgl;
|
||||
gl.viewport(0, 0, w, w);
|
||||
gl.scissor(0, 0, w, w);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export type ValueKind = keyof ValueKindType
|
||||
export type KindValue = UniformKindValue & DataTypeArrayType & TextureKindValue & ValueKindType
|
||||
|
||||
export type Values<S extends RenderableSchema> = { readonly [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> }
|
||||
export type UnboxedValues<S extends RenderableSchema> = { readonly [k in keyof S]: KindValue[S[k]['kind']] }
|
||||
|
||||
export function splitValues(schema: RenderableSchema, values: RenderableValues) {
|
||||
const attributeValues: AttributeValues = {};
|
||||
@@ -102,6 +103,7 @@ export type RenderableSchema = {
|
||||
}
|
||||
export type RenderableValues = { readonly [k: string]: ValueCell<any> }
|
||||
|
||||
|
||||
//
|
||||
|
||||
export const GlobalUniformSchema = {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { stringToWords } from '../mol-util/string';
|
||||
import { degToRad } from '../mol-math/misc';
|
||||
import { createNullTexture, Texture, Textures } from './webgl/texture';
|
||||
import { arrayMapUpsert } from '../mol-util/array';
|
||||
import { clamp } from '../mol-math/interpolate';
|
||||
|
||||
export interface RendererStats {
|
||||
programCount: number
|
||||
@@ -422,7 +423,6 @@ namespace Renderer {
|
||||
|
||||
const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
|
||||
@@ -475,8 +475,11 @@ namespace Renderer {
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
|
||||
// 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.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
|
||||
renderObject(r, 'colorWboit');
|
||||
}
|
||||
}
|
||||
@@ -488,8 +491,11 @@ namespace Renderer {
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
renderObject(r, 'colorWboit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ export type ShaderDefines = {
|
||||
[k: string]: ValueCell<DefineType>
|
||||
}
|
||||
|
||||
function getDefinesCode (defines: ShaderDefines) {
|
||||
function getDefinesCode(defines: ShaderDefines) {
|
||||
if (defines === undefined) return '';
|
||||
const lines = [];
|
||||
for (const name in defines) {
|
||||
|
||||
@@ -5,7 +5,8 @@ export default `
|
||||
discard;
|
||||
}
|
||||
} else if (uRenderWboit) {
|
||||
if (preFogAlpha != 1.0 && !interior && fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
|
||||
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
|
||||
if (preFogAlpha != 1.0 && !interior && (fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize) || fragmentDepth > 0.99)) {
|
||||
float alpha = gl_FragColor.a;
|
||||
float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0);
|
||||
gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);
|
||||
|
||||
229
src/mol-gl/shader/fxaa.frag.ts
Normal file
229
src/mol-gl/shader/fxaa.frag.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tColor;
|
||||
uniform vec2 uTexSizeInv;
|
||||
|
||||
// adapted from https://github.com/kosua20/Rendu
|
||||
// MIT License Copyright (c) 2017 Simon Rodriguez
|
||||
|
||||
#define QUALITY(q) ((q) < 5 ? 1.0 : ((q) > 5 ? ((q) < 10 ? 2.0 : ((q) < 11 ? 4.0 : 8.0)) : 1.5))
|
||||
|
||||
float rgb2luma(vec3 rgb){
|
||||
return sqrt(dot(rgb, vec3(0.299, 0.587, 0.114)));
|
||||
}
|
||||
|
||||
float sampleLuma(vec2 uv) {
|
||||
return rgb2luma(texture2D(tColor, uv).rgb);
|
||||
}
|
||||
|
||||
float sampleLuma(vec2 uv, float uOffset, float vOffset) {
|
||||
uv += uTexSizeInv * vec2(uOffset, vOffset);
|
||||
return sampleLuma(uv);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
|
||||
vec2 inverseScreenSize = uTexSizeInv;
|
||||
|
||||
vec4 colorCenter = texture2D(tColor, coords);
|
||||
|
||||
// Luma at the current fragment
|
||||
float lumaCenter = rgb2luma(colorCenter.rgb);
|
||||
|
||||
// Luma at the four direct neighbours of the current fragment.
|
||||
float lumaDown = sampleLuma(coords, 0.0, -1.0);
|
||||
float lumaUp = sampleLuma(coords, 0.0, 1.0);
|
||||
float lumaLeft = sampleLuma(coords, -1.0, 0.0);
|
||||
float lumaRight = sampleLuma(coords, 1.0, 0.0);
|
||||
|
||||
// Find the maximum and minimum luma around the current fragment.
|
||||
float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
|
||||
float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
|
||||
|
||||
// Compute the delta.
|
||||
float lumaRange = lumaMax - lumaMin;
|
||||
|
||||
// If the luma variation is lower that a threshold (or if we are in a really dark area),
|
||||
// we are not on an edge, don't perform any AA.
|
||||
if (lumaRange < max(dEdgeThresholdMin, lumaMax * dEdgeThresholdMax)) {
|
||||
gl_FragColor = colorCenter;
|
||||
return;
|
||||
}
|
||||
|
||||
// Query the 4 remaining corners lumas.
|
||||
float lumaDownLeft = sampleLuma(coords, -1.0, -1.0);
|
||||
float lumaUpRight = sampleLuma(coords, 1.0, 1.0);
|
||||
float lumaUpLeft = sampleLuma(coords, -1.0, 1.0);
|
||||
float lumaDownRight = sampleLuma(coords, 1.0, -1.0);
|
||||
|
||||
// Combine the four edges lumas (using intermediary variables for future computations
|
||||
// with the same values).
|
||||
float lumaDownUp = lumaDown + lumaUp;
|
||||
float lumaLeftRight = lumaLeft + lumaRight;
|
||||
|
||||
// Same for corners
|
||||
float lumaLeftCorners = lumaDownLeft + lumaUpLeft;
|
||||
float lumaDownCorners = lumaDownLeft + lumaDownRight;
|
||||
float lumaRightCorners = lumaDownRight + lumaUpRight;
|
||||
float lumaUpCorners = lumaUpRight + lumaUpLeft;
|
||||
|
||||
// Compute an estimation of the gradient along the horizontal and vertical axis.
|
||||
float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners);
|
||||
float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners);
|
||||
|
||||
// Is the local edge horizontal or vertical ?
|
||||
bool isHorizontal = (edgeHorizontal >= edgeVertical);
|
||||
|
||||
// Choose the step size (one pixel) accordingly.
|
||||
float stepLength = isHorizontal ? inverseScreenSize.y : inverseScreenSize.x;
|
||||
|
||||
// Select the two neighboring texels lumas in the opposite direction to the local edge.
|
||||
float luma1 = isHorizontal ? lumaDown : lumaLeft;
|
||||
float luma2 = isHorizontal ? lumaUp : lumaRight;
|
||||
// Compute gradients in this direction.
|
||||
float gradient1 = luma1 - lumaCenter;
|
||||
float gradient2 = luma2 - lumaCenter;
|
||||
|
||||
// Which direction is the steepest ?
|
||||
bool is1Steepest = abs(gradient1) >= abs(gradient2);
|
||||
|
||||
// Gradient in the corresponding direction, normalized.
|
||||
float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
|
||||
|
||||
// Average luma in the correct direction.
|
||||
float lumaLocalAverage = 0.0;
|
||||
if(is1Steepest){
|
||||
// Switch the direction
|
||||
stepLength = -stepLength;
|
||||
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
|
||||
} else {
|
||||
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
|
||||
}
|
||||
|
||||
// Shift UV in the correct direction by half a pixel.
|
||||
vec2 currentUv = coords;
|
||||
if(isHorizontal){
|
||||
currentUv.y += stepLength * 0.5;
|
||||
} else {
|
||||
currentUv.x += stepLength * 0.5;
|
||||
}
|
||||
|
||||
// Compute offset (for each iteration step) in the right direction.
|
||||
vec2 offset = isHorizontal ? vec2(inverseScreenSize.x, 0.0) : vec2(0.0, inverseScreenSize.y);
|
||||
// Compute UVs to explore on each side of the edge, orthogonally.
|
||||
// The QUALITY allows us to step faster.
|
||||
vec2 uv1 = currentUv - offset * QUALITY(0);
|
||||
vec2 uv2 = currentUv + offset * QUALITY(0);
|
||||
|
||||
// Read the lumas at both current extremities of the exploration segment,
|
||||
// and compute the delta wrt to the local average luma.
|
||||
float lumaEnd1 = sampleLuma(uv1);
|
||||
float lumaEnd2 = sampleLuma(uv2);
|
||||
lumaEnd1 -= lumaLocalAverage;
|
||||
lumaEnd2 -= lumaLocalAverage;
|
||||
|
||||
// If the luma deltas at the current extremities is larger than the local gradient,
|
||||
// we have reached the side of the edge.
|
||||
bool reached1 = abs(lumaEnd1) >= gradientScaled;
|
||||
bool reached2 = abs(lumaEnd2) >= gradientScaled;
|
||||
bool reachedBoth = reached1 && reached2;
|
||||
|
||||
// If the side is not reached, we continue to explore in this direction.
|
||||
if(!reached1){
|
||||
uv1 -= offset * QUALITY(1);
|
||||
}
|
||||
if(!reached2){
|
||||
uv2 += offset * QUALITY(1);
|
||||
}
|
||||
|
||||
// If both sides have not been reached, continue to explore.
|
||||
if(!reachedBoth){
|
||||
for(int i = 2; i < dIterations; i++){
|
||||
// If needed, read luma in 1st direction, compute delta.
|
||||
if(!reached1){
|
||||
lumaEnd1 = sampleLuma(uv1);
|
||||
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
|
||||
}
|
||||
// If needed, read luma in opposite direction, compute delta.
|
||||
if(!reached2){
|
||||
lumaEnd2 = sampleLuma(uv2);
|
||||
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
|
||||
}
|
||||
// If the luma deltas at the current extremities is larger than the local gradient,
|
||||
// we have reached the side of the edge.
|
||||
reached1 = abs(lumaEnd1) >= gradientScaled;
|
||||
reached2 = abs(lumaEnd2) >= gradientScaled;
|
||||
reachedBoth = reached1 && reached2;
|
||||
|
||||
// If the side is not reached, we continue to explore in this direction,
|
||||
// with a variable quality.
|
||||
if(!reached1){
|
||||
uv1 -= offset * QUALITY(i);
|
||||
}
|
||||
if(!reached2){
|
||||
uv2 += offset * QUALITY(i);
|
||||
}
|
||||
|
||||
// If both sides have been reached, stop the exploration.
|
||||
if(reachedBoth){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the distances to each side edge of the edge (!).
|
||||
float distance1 = isHorizontal ? (coords.x - uv1.x) : (coords.y - uv1.y);
|
||||
float distance2 = isHorizontal ? (uv2.x - coords.x) : (uv2.y - coords.y);
|
||||
|
||||
// In which direction is the side of the edge closer ?
|
||||
bool isDirection1 = distance1 < distance2;
|
||||
float distanceFinal = min(distance1, distance2);
|
||||
|
||||
// Thickness of the edge.
|
||||
float edgeThickness = (distance1 + distance2);
|
||||
|
||||
// Is the luma at center smaller than the local average ?
|
||||
bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
|
||||
|
||||
// If the luma at center is smaller than at its neighbour,
|
||||
// the delta luma at each end should be positive (same variation).
|
||||
bool correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
|
||||
bool correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
|
||||
|
||||
// Only keep the result in the direction of the closer side of the edge.
|
||||
bool correctVariation = isDirection1 ? correctVariation1 : correctVariation2;
|
||||
|
||||
// UV offset: read in the direction of the closest side of the edge.
|
||||
float pixelOffset = - distanceFinal / edgeThickness + 0.5;
|
||||
|
||||
// If the luma variation is incorrect, do not offset.
|
||||
float finalOffset = correctVariation ? pixelOffset : 0.0;
|
||||
|
||||
// Sub-pixel shifting
|
||||
// Full weighted average of the luma over the 3x3 neighborhood.
|
||||
float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
|
||||
// Ratio of the delta between the global average and the center luma,
|
||||
// over the luma range in the 3x3 neighborhood.
|
||||
float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
|
||||
float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
|
||||
// Compute a sub-pixel offset based on this delta.
|
||||
float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * float(dSubpixelQuality);
|
||||
|
||||
// Pick the biggest of the two offsets.
|
||||
finalOffset = max(finalOffset, subPixelOffsetFinal);
|
||||
|
||||
// Compute the final UV coordinates.
|
||||
vec2 finalUv = coords;
|
||||
if(isHorizontal){
|
||||
finalUv.y += finalOffset * stepLength;
|
||||
} else {
|
||||
finalUv.x += finalOffset * stepLength;
|
||||
}
|
||||
|
||||
// Read the color at the new UV coordinates, and use it.
|
||||
gl_FragColor = texture2D(tColor, finalUv);
|
||||
}
|
||||
`;
|
||||
181
src/mol-gl/shader/util/grid3d-template.frag.ts
Normal file
181
src/mol-gl/shader/util/grid3d-template.frag.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform vec2 uQuadShift;
|
||||
uniform vec3 uDimensions;
|
||||
uniform vec3 uMin;
|
||||
uniform vec3 uDelta;
|
||||
uniform bool uLittleEndian;
|
||||
uniform float uWidth;
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
uniform sampler2D tCumulativeSum;
|
||||
#endif
|
||||
|
||||
{UNIFORMS}
|
||||
|
||||
{UTILS}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
|
||||
// MIT License, Copyright (c) 2020 Equinor
|
||||
|
||||
float shiftRight (float v, float amt) {
|
||||
v = floor(v) + 0.5;
|
||||
return floor(v / exp2(amt));
|
||||
}
|
||||
float shiftLeft (float v, float amt) {
|
||||
return floor(v * exp2(amt) + 0.5);
|
||||
}
|
||||
float maskLast (float v, float bits) {
|
||||
return mod(v, shiftLeft(1.0, bits));
|
||||
}
|
||||
float extractBits (float num, float from, float to) {
|
||||
from = floor(from + 0.5); to = floor(to + 0.5);
|
||||
return maskLast(shiftRight(num, from), to - from);
|
||||
}
|
||||
|
||||
vec4 floatToRgba(float texelFloat) {
|
||||
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
|
||||
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
|
||||
texelFloat = abs(texelFloat);
|
||||
float exponent = floor(log2(texelFloat));
|
||||
float biased_exponent = exponent + 127.0;
|
||||
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
|
||||
float t = biased_exponent / 2.0;
|
||||
float last_bit_of_biased_exponent = fract(t) * 2.0;
|
||||
float remaining_bits_of_biased_exponent = floor(t);
|
||||
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
|
||||
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
|
||||
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
|
||||
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
|
||||
return (
|
||||
uLittleEndian
|
||||
? vec4(byte4, byte3, byte2, byte1)
|
||||
: vec4(byte1, byte2, byte3, byte4)
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
// OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
|
||||
ivec4 floatsToBytes(vec4 inputFloats) {
|
||||
ivec4 bytes = ivec4(inputFloats * 255.0);
|
||||
return (
|
||||
uLittleEndian
|
||||
? bytes.abgr
|
||||
: bytes
|
||||
);
|
||||
}
|
||||
|
||||
// Break the four bytes down into an array of 32 bits.
|
||||
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
|
||||
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
|
||||
float acc = float(bytes[channelIndex]);
|
||||
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
|
||||
float powerOfTwo = exp2(float(indexInByte));
|
||||
bool bit = acc >= powerOfTwo;
|
||||
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
|
||||
acc = mod(acc, powerOfTwo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the exponent of the 32-bit float.
|
||||
float getExponent(bool bits[32]) {
|
||||
const int startIndex = 1;
|
||||
const int bitStringLength = 8;
|
||||
const int endBeforeIndex = startIndex + bitStringLength;
|
||||
float acc = 0.0;
|
||||
int pow2 = bitStringLength - 1;
|
||||
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
|
||||
acc += float(bits[bitIndex]) * exp2(float(pow2--));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Compute the mantissa of the 32-bit float.
|
||||
float getMantissa(bool bits[32], bool subnormal) {
|
||||
const int startIndex = 9;
|
||||
const int bitStringLength = 23;
|
||||
const int endBeforeIndex = startIndex + bitStringLength;
|
||||
// Leading/implicit/hidden bit convention:
|
||||
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
|
||||
float acc = float(!subnormal) * exp2(float(bitStringLength));
|
||||
int pow2 = bitStringLength - 1;
|
||||
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
|
||||
acc += float(bits[bitIndex]) * exp2(float(pow2--));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Parse the float from its 32 bits.
|
||||
float bitsToFloat(bool bits[32]) {
|
||||
float signBit = float(bits[0]) * -2.0 + 1.0;
|
||||
float exponent = getExponent(bits);
|
||||
bool subnormal = abs(exponent - 0.0) < 0.01;
|
||||
float mantissa = getMantissa(bits, subnormal);
|
||||
float exponentBias = 127.0;
|
||||
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
|
||||
}
|
||||
|
||||
float rgbaToFloat(vec4 texelRGBA) {
|
||||
ivec4 rgbaBytes = floatsToBytes(texelRGBA);
|
||||
bool bits[32];
|
||||
bytesToBits(rgbaBytes, bits);
|
||||
return bitsToFloat(bits);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
float intDiv(float a, float b) { return float(int(a) / int(b)); }
|
||||
float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
|
||||
|
||||
void main(void) {
|
||||
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
|
||||
|
||||
// axis order fast to slow Z, Y, X
|
||||
// TODO: support arbitrary axis orders?
|
||||
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
|
||||
float j = intMod(kk, uDimensions.y);
|
||||
float i = intDiv(kk, uDimensions.y);
|
||||
|
||||
vec3 xyz = uMin + uDelta * vec3(i, j, k);
|
||||
|
||||
{MAIN}
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)));
|
||||
#endif
|
||||
gl_FragColor = floatToRgba({RETURN});
|
||||
}
|
||||
`;
|
||||
@@ -22,7 +22,7 @@ import { VertexArray, createVertexArray } from './vertex-array';
|
||||
|
||||
function defineValueHash(v: boolean | number | string): number {
|
||||
return typeof v === 'boolean' ? (v ? 1 : 0) :
|
||||
typeof v === 'number' ? v : hashString(v);
|
||||
typeof v === 'number' ? (v * 10000) : hashString(v);
|
||||
}
|
||||
|
||||
function wrapCached<T extends Resource>(resourceItem: ReferenceItem<T>) {
|
||||
|
||||
@@ -79,4 +79,30 @@ export function getUniformSetters(schema: RenderableSchema) {
|
||||
}
|
||||
});
|
||||
return setters;
|
||||
}
|
||||
|
||||
export function getUniformGlslType(kind: UniformKind): string {
|
||||
switch (kind) {
|
||||
case 'f': return 'float';
|
||||
case 'i': return 'int';
|
||||
case 't': return 'sampler2D';
|
||||
case 'b': return 'bool';
|
||||
case 'v2': return 'vec2';
|
||||
case 'v3': return 'vec3';
|
||||
case 'v4': return 'vec4';
|
||||
case 'm3': return 'mat3';
|
||||
case 'm4': return 'mat4';
|
||||
}
|
||||
throw new Error(`${kind} has no primitive GLSL type.`);
|
||||
}
|
||||
|
||||
export function isUniformValueScalar(kind: UniformKind): boolean {
|
||||
switch (kind) {
|
||||
case 'f':
|
||||
case 'i':
|
||||
case 'b':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ReaderResult as Result } from '../result';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { parseCsv } from '../csv/parser';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { toTable } from '../cif/schema';
|
||||
|
||||
import Schema = Column.Schema
|
||||
import { CsvTable } from '../csv/data-model';
|
||||
|
||||
|
||||
export const Schema3DG = {
|
||||
/** Chromosome name */
|
||||
chromosome: Schema.str,
|
||||
/** Base position */
|
||||
position: Schema.int,
|
||||
/** X coordinate */
|
||||
x: Schema.float,
|
||||
/** Y coordinate */
|
||||
y: Schema.float,
|
||||
/** Z coordinate */
|
||||
z: Schema.float,
|
||||
};
|
||||
export type Schema3DG = typeof Schema3DG
|
||||
|
||||
export interface File3DG {
|
||||
table: Table<Schema3DG>
|
||||
}
|
||||
|
||||
const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ];
|
||||
|
||||
function categoryFromTable(name: string, table: CsvTable) {
|
||||
return {
|
||||
name,
|
||||
rowCount: table.rowCount,
|
||||
fieldNames: FieldNames,
|
||||
getField: (name: string) => {
|
||||
return table.getColumn(FieldNames.indexOf(name).toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function parse3DG(data: string) {
|
||||
return Task.create<Result<File3DG>>('Parse 3DG', async ctx => {
|
||||
const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true };
|
||||
const csvFile = await parseCsv(data, opts).runInContext(ctx);
|
||||
if (csvFile.isError) return Result.error(csvFile.message, csvFile.line);
|
||||
const category = categoryFromTable('3dg', csvFile.result.table);
|
||||
const table = toTable(Schema3DG, category);
|
||||
return Result.success({ table });
|
||||
});
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { parse3DG } from '../3dg/parser';
|
||||
|
||||
const basic3dgString = `1(mat) 1420000 0.791377837067 10.9947291355 -13.1882897693
|
||||
1(mat) 1440000 -0.268241283699 10.5200875887 -13.0896257278
|
||||
1(mat) 1460000 -1.3853075236 10.5513787498 -13.1440142173
|
||||
1(mat) 1480000 -1.55984101733 11.4340829129 -13.6026301209
|
||||
1(mat) 1500000 -0.770991778399 11.4758488546 -14.5881137222
|
||||
1(mat) 1520000 -0.0848245107875 12.2624690808 -14.354289628
|
||||
1(mat) 1540000 -0.458643807046 12.5985791771 -13.4701149287
|
||||
1(mat) 1560000 -0.810322906201 12.2461643989 -12.3172933413
|
||||
1(mat) 1580000 -2.08211172035 12.8886838656 -12.8742007778
|
||||
1(mat) 1600000 -3.52093948201 13.1850935438 -12.4118684428`;
|
||||
|
||||
describe('3dg reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await parse3DG(basic3dgString).run();
|
||||
expect(parsed.isError).toBe(false);
|
||||
|
||||
if (parsed.isError) return;
|
||||
const { chromosome, position, x, y, z } = parsed.result.table;
|
||||
expect(chromosome.value(0)).toBe('1(mat)');
|
||||
expect(position.value(1)).toBe(1440000);
|
||||
expect(x.value(5)).toBe(-0.0848245107875);
|
||||
expect(y.value(5)).toBe(12.2624690808);
|
||||
expect(z.value(5)).toBe(-14.354289628);
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,15 @@ export type DensityTextureData = {
|
||||
gridTexScale: Vec2
|
||||
}
|
||||
|
||||
export interface RegularGrid3d {
|
||||
box: Box3D,
|
||||
dimensions: Vec3
|
||||
}
|
||||
|
||||
export function getRegularGrid3dDelta({ box, dimensions }: RegularGrid3d) {
|
||||
return Vec3.div(Vec3(), Box3D.size(Vec3(), box), Vec3.subScalar(Vec3(), dimensions, 1));
|
||||
}
|
||||
|
||||
export function fillGridDim(length: number, start: number, step: number) {
|
||||
const a = new Float32Array(length);
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { File3DG } from '../../mol-io/reader/3dg/parser';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { createModels } from './basic/parser';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
|
||||
function getBasic(table: File3DG['table']) {
|
||||
const entityIds = new Array<string>(table._rowCount);
|
||||
const entityBuilder = new EntityBuilder();
|
||||
|
||||
const seqIdStarts = table.position.toArray({ array: Uint32Array });
|
||||
const seqIdEnds = new Uint32Array(table._rowCount);
|
||||
const stride = seqIdStarts[1] - seqIdStarts[0];
|
||||
|
||||
const objectRadius = stride / 3500;
|
||||
|
||||
for (let i = 0, il = table._rowCount; i < il; ++i) {
|
||||
const chr = table.chromosome.value(i);
|
||||
const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr);
|
||||
entityIds[i] = entityId;
|
||||
seqIdEnds[i] = seqIdStarts[i] + stride - 1;
|
||||
}
|
||||
|
||||
const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, {
|
||||
id: Column.ofIntArray(fillSerial(new Uint32Array(table._rowCount))),
|
||||
entity_id: Column.ofStringArray(entityIds),
|
||||
seq_id_begin: Column.ofIntArray(seqIdStarts),
|
||||
seq_id_end: Column.ofIntArray(seqIdEnds),
|
||||
asym_id: table.chromosome,
|
||||
|
||||
Cartn_x: Column.ofFloatArray(Column.mapToArray(table.x, x => x * 10, Float32Array)),
|
||||
Cartn_y: Column.ofFloatArray(Column.mapToArray(table.y, y => y * 10, Float32Array)),
|
||||
Cartn_z: Column.ofFloatArray(Column.mapToArray(table.z, z => z * 10, Float32Array)),
|
||||
|
||||
object_radius: Column.ofConst(objectRadius, table._rowCount, Column.Schema.float),
|
||||
rmsf: Column.ofConst(0, table._rowCount, Column.Schema.float),
|
||||
model_id: Column.ofConst(1, table._rowCount, Column.Schema.int),
|
||||
}, table._rowCount);
|
||||
|
||||
return createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
|
||||
model_id: Column.ofIntArray([1]),
|
||||
model_name: Column.ofStringArray(['3DG Model']),
|
||||
}, 1),
|
||||
ihm_sphere_obj_site
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { Format3dg };
|
||||
|
||||
type Format3dg = ModelFormat<File3DG>
|
||||
|
||||
namespace Format3dg {
|
||||
export function is(x: ModelFormat): x is Format3dg {
|
||||
return x.kind === '3dg';
|
||||
}
|
||||
|
||||
export function from3dg(file3dg: File3DG): Format3dg {
|
||||
return { kind: '3dg', name: '3DG', data: file3dg };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFrom3DG(file3dg: File3DG): Task<Trajectory> {
|
||||
return Task.create('Parse 3DG', async ctx => {
|
||||
const format = Format3dg.from3dg(file3dg);
|
||||
const basic = getBasic(file3dg.table);
|
||||
return createModels(basic, format, ctx);
|
||||
});
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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 { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
function modelSymmetryFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -69,6 +70,8 @@ function structConnFromMmcif(model: Model) {
|
||||
}
|
||||
StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif);
|
||||
|
||||
GlobalModelTransformInfo.Provider.formatRegistry.add('mmCIF', GlobalModelTransformInfo.fromMmCif, GlobalModelTransformInfo.hasData);
|
||||
|
||||
//
|
||||
|
||||
export { MmcifFormat };
|
||||
|
||||
@@ -83,13 +83,16 @@ export const _atom_site: CifCategory<CifExportContext> = {
|
||||
}
|
||||
};
|
||||
|
||||
function prepostfixed(prefix: string | undefined, postfix: string | undefined, name: string) {
|
||||
if (prefix && postfix) return `${prefix}_${name}_${postfix}`;
|
||||
function prepostfixed(prefix: string | undefined, name: string) {
|
||||
if (prefix) return `${prefix}_${name}`;
|
||||
if (postfix) return `${name}_${postfix}`;
|
||||
return name;
|
||||
}
|
||||
|
||||
function prefixedInsCode(prefix: string | undefined) {
|
||||
if (!prefix) return 'pdbx_PDB_ins_code';
|
||||
return `pdbx_${prefix}_PDB_ins_code`;
|
||||
}
|
||||
|
||||
function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement.Location, prop: (e: StructureElement.Location) => any) {
|
||||
return (k: K, d: D) => prop(loc(k, d));
|
||||
}
|
||||
@@ -102,15 +105,14 @@ function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: (
|
||||
|
||||
export interface IdFieldsOptions {
|
||||
prefix?: string,
|
||||
postfix?: string,
|
||||
includeModelNum?: boolean
|
||||
}
|
||||
|
||||
export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifWriter.fields<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
.str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
encoder: E.deltaRLE,
|
||||
valueKind: (k, d) => {
|
||||
const e = getLocation(k, d);
|
||||
@@ -118,45 +120,45 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur
|
||||
return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
|
||||
}
|
||||
})
|
||||
.str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
.str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
.str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
}
|
||||
|
||||
export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifField.build<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
}
|
||||
|
||||
export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifField.build<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id));
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
}
|
||||
|
||||
export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifWriter.fields<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
.str(prepostfixed(prefix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
|
||||
.str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
encoder: E.deltaRLE,
|
||||
valueKind: (k, d) => {
|
||||
const e = getLocation(k, d);
|
||||
@@ -164,16 +166,16 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl
|
||||
return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
|
||||
}
|
||||
})
|
||||
.str(prepostfixed(prefix, postfix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id))
|
||||
.str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
.str(prepostfixed(prefix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id))
|
||||
.str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
|
||||
.str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
.str(prepostfixed(prefix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
|
||||
.str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
|
||||
@@ -132,7 +132,8 @@ function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifE
|
||||
type encode_mmCIF_categories_Params = {
|
||||
skipCategoryNames?: Set<string>,
|
||||
exportCtx?: CifExportContext,
|
||||
copyAllCategories?: boolean
|
||||
copyAllCategories?: boolean,
|
||||
customProperties?: CustomPropertyDescriptor[]
|
||||
}
|
||||
|
||||
/** Doesn't start a data block */
|
||||
@@ -144,7 +145,7 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
|
||||
const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
|
||||
|
||||
if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
|
||||
encode_mmCIF_categories_copyAll(encoder, ctx);
|
||||
encode_mmCIF_categories_copyAll(encoder, ctx, params);
|
||||
} else {
|
||||
encode_mmCIF_categories_default(encoder, ctx, params);
|
||||
}
|
||||
@@ -168,6 +169,14 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
|
||||
}
|
||||
}
|
||||
|
||||
if (params?.customProperties) {
|
||||
for (const customProp of params?.customProperties) {
|
||||
for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
encoder.writeCategory(cat, propCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
if (!s.hasCustomProperties) continue;
|
||||
for (const customProp of s.customPropertyDescriptors.all) {
|
||||
@@ -178,7 +187,7 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
|
||||
}
|
||||
}
|
||||
|
||||
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) {
|
||||
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) {
|
||||
const providedCategories = new Map<string, CifExportCategoryInfo>();
|
||||
|
||||
for (const cat of Categories) {
|
||||
@@ -188,12 +197,21 @@ function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExp
|
||||
const mapping = atom_site_operator_mapping(ctx);
|
||||
if (mapping) providedCategories.set(mapping[0].name, mapping);
|
||||
|
||||
const _params = params || { };
|
||||
for (const customProp of ctx.firstModel.customProperties.all) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx)) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
providedCategories.set(info[0].name, info);
|
||||
}
|
||||
}
|
||||
|
||||
if (params?.customProperties) {
|
||||
for (const customProp of params?.customProperties) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
providedCategories.set(info[0].name, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
if (!s.hasCustomProperties) continue;
|
||||
for (const customProp of s.customPropertyDescriptors.all) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// TODO add access to things like MOL2 charge ...
|
||||
80
src/mol-model/structure/model/properties/global-transform.ts
Normal file
80
src/mol-model/structure/model/properties/global-transform.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4, Tensor } from '../../../../mol-math/linear-algebra';
|
||||
import { FormatPropertyProvider } from '../../../../mol-model-formats/structure/common/property';
|
||||
import { CustomPropertyDescriptor } from '../../../custom-property';
|
||||
import { CifExportContext } from '../../structure';
|
||||
import { Model } from '../model';
|
||||
import { Column, Table } from '../../../../mol-data/db';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { toTable } from '../../../../mol-io/reader/cif/schema';
|
||||
|
||||
export namespace GlobalModelTransformInfo {
|
||||
const CategoryName = 'molstar_global_model_transform_info' as const;
|
||||
export const Schema = {
|
||||
[CategoryName]: {
|
||||
matrix: Column.Schema.Matrix(4, 4, Column.Schema.float)
|
||||
}
|
||||
};
|
||||
export type Schema = typeof Schema
|
||||
|
||||
export const Descriptor = CustomPropertyDescriptor({
|
||||
name: CategoryName,
|
||||
cifExport: {
|
||||
categories: [{
|
||||
name: CategoryName,
|
||||
instance(ctx: CifExportContext) {
|
||||
const mat = get(ctx.firstModel);
|
||||
if (!mat) return CifWriter.Category.Empty;
|
||||
const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: mat as unknown as Tensor.Data }]);
|
||||
return CifWriter.Category.ofTable(table);
|
||||
}
|
||||
}],
|
||||
prefix: 'molstar'
|
||||
}
|
||||
});
|
||||
|
||||
export const Provider = FormatPropertyProvider.create<Mat4>(Descriptor);
|
||||
|
||||
export function attach(model: Model, matrix: Mat4) {
|
||||
if (!model.customProperties.has(Descriptor)) {
|
||||
model.customProperties.add(Descriptor);
|
||||
}
|
||||
Provider.set(model, matrix);
|
||||
}
|
||||
|
||||
export function get(model: Model): Mat4 | undefined {
|
||||
return Provider.get(model);
|
||||
}
|
||||
|
||||
export function fromMmCif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
|
||||
const cat = model.sourceData.data.frame.categories[CategoryName];
|
||||
if (!cat) return;
|
||||
const table = toTable(Schema[CategoryName], cat);
|
||||
if (table._rowCount === 0) return;
|
||||
return table.matrix.value(0) as unknown as Mat4;
|
||||
}
|
||||
|
||||
export function hasData(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const cat = model.sourceData.data.frame.categories[CategoryName];
|
||||
return !!cat && cat.rowCount > 0;
|
||||
}
|
||||
|
||||
export function writeMmCif(encoder: CifWriter.Encoder, matrix: Mat4) {
|
||||
encoder.writeCategory({
|
||||
name: CategoryName,
|
||||
instance() {
|
||||
const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: matrix as unknown as Tensor.Data }]);
|
||||
return CifWriter.Category.ofTable(table);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { UniqueArray } from '../../../../mol-data/generic';
|
||||
import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import Structure from '../structure';
|
||||
import Unit from '../unit';
|
||||
@@ -489,7 +489,7 @@ export namespace Loci {
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98');
|
||||
const tempPosBoundary = Vec3();
|
||||
export function getBoundary(loci: Loci): Boundary {
|
||||
export function getBoundary(loci: Loci, transform?: Mat4): Boundary {
|
||||
boundaryHelper.reset();
|
||||
|
||||
for (const e of loci.elements) {
|
||||
@@ -499,6 +499,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
|
||||
boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
@@ -510,6 +511,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
|
||||
boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ const defaultPreset = TrajectoryHierarchyPresetProvider({
|
||||
|
||||
const AllModelsParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
useDefaultIfSingleModel: PD.Optional(PD.Boolean(false)),
|
||||
representationPresetParams: PD.Optional(PD.Group(StructureRepresentationPresetProvider.CommonParams)),
|
||||
...CommonParams(a, plugin)
|
||||
});
|
||||
|
||||
|
||||
@@ -113,15 +113,6 @@ export const GroProvider: TrajectoryFormatProvider = {
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const Provider3dg: TrajectoryFormatProvider = {
|
||||
label: '3DG',
|
||||
description: '3DG',
|
||||
category: TrajectoryFormatCategory,
|
||||
stringExtensions: ['3dg'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFrom3DG),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const MolProvider: TrajectoryFormatProvider = {
|
||||
label: 'MOL/SDF',
|
||||
description: 'MOL/SDF',
|
||||
@@ -146,7 +137,6 @@ export const BuiltInTrajectoryFormats = [
|
||||
['pdb', PdbProvider] as const,
|
||||
['pdbqt', PdbqtProvider] as const,
|
||||
['gro', GroProvider] as const,
|
||||
['3dg', Provider3dg] as const,
|
||||
['mol', MolProvider] as const,
|
||||
['mol2', Mol2Provider] as const,
|
||||
] as const;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { File3DG } from '../mol-io/reader/3dg/parser';
|
||||
import { Ccp4File } from '../mol-io/reader/ccp4/schema';
|
||||
import { CifFile } from '../mol-io/reader/cif';
|
||||
import { DcdFile } from '../mol-io/reader/dcd/parser';
|
||||
@@ -83,7 +82,6 @@ export namespace PluginStateObject {
|
||||
{ kind: 'cif', data: CifFile } |
|
||||
{ kind: 'pdb', data: CifFile } |
|
||||
{ kind: 'gro', data: CifFile } |
|
||||
{ kind: '3dg', data: File3DG } |
|
||||
{ kind: 'dcd', data: DcdFile } |
|
||||
{ kind: 'ccp4', data: Ccp4File } |
|
||||
{ kind: 'dsn6', data: Dsn6File } |
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { parse3DG } from '../../mol-io/reader/3dg/parser';
|
||||
import { parseDcd } from '../../mol-io/reader/dcd/parser';
|
||||
import { parseGRO } from '../../mol-io/reader/gro/parser';
|
||||
import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
|
||||
import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
|
||||
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
|
||||
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
|
||||
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
@@ -52,7 +50,6 @@ export { TrajectoryFromMOL };
|
||||
export { TrajectoryFromMOL2 };
|
||||
export { TrajectoryFromCube };
|
||||
export { TrajectoryFromCifCore };
|
||||
export { TrajectoryFrom3DG };
|
||||
export { ModelFromTrajectory };
|
||||
export { StructureFromTrajectory };
|
||||
export { StructureFromModel };
|
||||
@@ -339,24 +336,6 @@ const TrajectoryFromCifCore = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFrom3DG = typeof TrajectoryFrom3DG
|
||||
const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-3dg',
|
||||
display: { name: 'Parse 3DG', description: 'Parse 3DG string and create trajectory.' },
|
||||
from: [SO.Data.String],
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse 3DG', async ctx => {
|
||||
const parsed = await parse3DG(a.data).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
const models = await trajectoryFrom3DG(parsed.result).runInContext(ctx);
|
||||
const props = trajectoryProps(models);
|
||||
return new SO.Molecule.Trajectory(models, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
|
||||
type ModelFromTrajectory = typeof ModelFromTrajectory
|
||||
const ModelFromTrajectory = PluginStateTransform.BuiltIn({
|
||||
|
||||
@@ -131,7 +131,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params);
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params));
|
||||
|
||||
// TODO set initial state, repr.setState({})
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
|
||||
@@ -205,6 +204,8 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
unwindStructureAssembly(structure, unitTransforms, newParams.t);
|
||||
@@ -240,6 +241,8 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.source.data;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
explodeStructure(structure.root, unitTransforms, newParams.t);
|
||||
@@ -287,6 +290,8 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -337,6 +342,8 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -383,6 +390,8 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofScript(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -430,6 +439,8 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -476,6 +487,8 @@ const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofScript(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -523,6 +536,8 @@ const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofBundle(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -619,7 +634,6 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
// TODO: allow for small molecules
|
||||
return oldParams.type.name === newParams.type.name;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
@@ -627,10 +641,10 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
const provider = plugin.representation.volume.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
const props = params.type.params || {};
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
|
||||
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
|
||||
// TODO set initial state, repr.setState({})
|
||||
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
});
|
||||
@@ -673,7 +687,6 @@ const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return Task.create('Shape Representation', async ctx => {
|
||||
const props = { ...PD.getDefaultValues(a.data.params), ...params };
|
||||
const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils);
|
||||
// TODO set initial state, repr.setState({})
|
||||
await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
|
||||
});
|
||||
|
||||
@@ -186,6 +186,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
|
||||
case 'file-list': return FileListControl;
|
||||
case 'select': return SelectControl;
|
||||
case 'value-ref': return ValueRefControl;
|
||||
case 'data-ref': return void 0;
|
||||
case 'text': return TextControl;
|
||||
case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined'
|
||||
? BoundedIntervalControl : IntervalControl;
|
||||
|
||||
@@ -44,7 +44,7 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action);
|
||||
this.ctx.canvas3d.mark(interactionLoci, action);
|
||||
}
|
||||
register() {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
|
||||
@@ -57,12 +57,14 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
let matched = false;
|
||||
|
||||
if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly(current);
|
||||
// remove repr to highlight loci everywhere on hover
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: current.loci });
|
||||
matched = true;
|
||||
}
|
||||
|
||||
if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend(current);
|
||||
// remove repr to highlight loci everywhere on hover
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci: current.loci });
|
||||
matched = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { Volume, Grid } from '../../../../mol-model/volume';
|
||||
import { VolumeServerHeader, VolumeServerInfo } from './model';
|
||||
import { Box3D } from '../../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { PluginBehavior } from '../../behavior';
|
||||
import { LRUCache } from '../../../../mol-util/lru-cache';
|
||||
@@ -23,6 +23,7 @@ import { StructureElement, Structure } from '../../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../context';
|
||||
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
|
||||
|
||||
@@ -302,6 +303,7 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
}
|
||||
|
||||
private _invTransform: Mat4 = Mat4();
|
||||
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
|
||||
if (Loci.isEmpty(loci)) {
|
||||
return Box3D();
|
||||
@@ -312,11 +314,16 @@ export namespace VolumeStreaming {
|
||||
const root = this.getStructureRoot();
|
||||
if (!root || root.obj?.data !== parent.obj?.data) return Box3D();
|
||||
|
||||
const transform = GlobalModelTransformInfo.get(root.obj?.data.models[0]!);
|
||||
if (transform) Mat4.invert(this._invTransform, transform);
|
||||
|
||||
const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci);
|
||||
const box = StructureElement.Loci.getBoundary(extendedLoci).box;
|
||||
const box = StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box;
|
||||
|
||||
if (StructureElement.Loci.size(extendedLoci) === 1) {
|
||||
Box3D.expand(box, box, Vec3.create(1, 1, 1));
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
@@ -360,7 +367,6 @@ export namespace VolumeStreaming {
|
||||
const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box';
|
||||
|
||||
this.params = params;
|
||||
|
||||
let box: Box3D | undefined = void 0, emptyData = false;
|
||||
|
||||
switch (params.entry.params.view.name) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Box3D } from '../../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { PluginConfig } from '../../../config';
|
||||
import { Model } from '../../../../mol-model/structure';
|
||||
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
|
||||
entries.push({
|
||||
@@ -253,20 +254,22 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
|
||||
channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true })
|
||||
}
|
||||
})({
|
||||
apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
apply: ({ a, params: srcParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
const channel = a.data.channels[srcParams.channel];
|
||||
if (!channel) return StateObject.Null;
|
||||
|
||||
const params = createVolumeProps(a.data, srcParams.channel);
|
||||
|
||||
const provider = VolumeRepresentationRegistry.BuiltIn.isosurface;
|
||||
const props = params.type.params || {};
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
|
||||
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
|
||||
const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
|
||||
const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
|
||||
await repr.createOrUpdate(props, channel.data).runInContext(ctx);
|
||||
if (transform) repr.setState({ transform });
|
||||
return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
|
||||
}),
|
||||
update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
// TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
|
||||
|
||||
const channel = a.data.channels[newParams.channel];
|
||||
@@ -277,6 +280,13 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...params.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
|
||||
await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
|
||||
|
||||
// TODO: set the transform here as well in case the structure moves?
|
||||
// doing this here now breaks the code for some reason...
|
||||
// const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
|
||||
// const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
|
||||
// if (transform) b.data.repr.setState({ transform });
|
||||
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
})
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ export const PluginConfig = {
|
||||
General: {
|
||||
IsBusyTimeoutMs: item('plugin-config.is-busy-timeout', 750),
|
||||
DisableAntialiasing: item('plugin-config.disable-antialiasing', false),
|
||||
PixelScale: item<number | undefined>('plugin-config.pixel-scale', void 0),
|
||||
PixelScale: item('plugin-config.pixel-scale', 1),
|
||||
EnableWboit: item('plugin-config.enable-wboit', false)
|
||||
},
|
||||
State: {
|
||||
|
||||
@@ -115,7 +115,10 @@ class ViewportScreenshotHelper extends PluginComponent {
|
||||
mode: mutlisample ? 'on' : 'off',
|
||||
sampleLevel: colorBufferFloat && textureFloat ? 4 : 2
|
||||
},
|
||||
postprocessing: c.props.postprocessing
|
||||
postprocessing: {
|
||||
...c.props.postprocessing,
|
||||
antialiasing: { name: 'off', params: {} }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,7 +134,10 @@ class ViewportScreenshotHelper extends PluginComponent {
|
||||
cameraHelper: { axes: this.values.axes },
|
||||
transparentBackground: this.values.transparent,
|
||||
// TODO: optimize because this creates a copy of a large object!
|
||||
postprocessing: this.plugin.canvas3d!.props.postprocessing
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
antialiasing: { name: 'off', params: {} }
|
||||
}
|
||||
});
|
||||
return this._imagePass;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { Visual } from '../visual';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
|
||||
export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
|
||||
|
||||
@@ -216,7 +217,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
|
||||
Representation.updateState(_state, state);
|
||||
},
|
||||
setTheme(theme: Theme) {
|
||||
console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
|
||||
if(isDebugMode) {
|
||||
console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
// TODO
|
||||
|
||||
@@ -131,7 +131,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 7, cylinderProps);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
|
||||
const order = LinkStyle.Double ? 2 : 3;
|
||||
const order = linkStyle === LinkStyle.Double ? 2 : 3;
|
||||
const multiRadius = linkRadius * (linkScale / (0.5 * order));
|
||||
const absOffset = (linkRadius - multiRadius) * linkSpacing;
|
||||
|
||||
|
||||
@@ -328,6 +328,8 @@ class State {
|
||||
const oldTree = this._tree;
|
||||
this._tree = _tree;
|
||||
|
||||
const cells = this.cells;
|
||||
|
||||
const ctx: UpdateContext = {
|
||||
parent: this,
|
||||
editInfo: StateBuilder.is(tree) ? tree.editInfo : void 0,
|
||||
@@ -346,7 +348,9 @@ class State {
|
||||
changed: false,
|
||||
hadError: false,
|
||||
wasAborted: false,
|
||||
newCurrent: void 0
|
||||
newCurrent: void 0,
|
||||
|
||||
getCellData: ref => cells.get(ref)!.obj?.data
|
||||
};
|
||||
|
||||
this.errorFree = true;
|
||||
@@ -454,7 +458,9 @@ interface UpdateContext {
|
||||
changed: boolean,
|
||||
hadError: boolean,
|
||||
wasAborted: boolean,
|
||||
newCurrent?: Ref
|
||||
newCurrent?: Ref,
|
||||
|
||||
getCellData: (ref: string) => any
|
||||
}
|
||||
|
||||
async function update(ctx: UpdateContext) {
|
||||
@@ -841,7 +847,7 @@ function resolveParams(ctx: UpdateContext, transform: StateTransform, src: State
|
||||
(transform.params as any) = transform.params
|
||||
? assignIfUndefined(transform.params, defaultValues)
|
||||
: defaultValues;
|
||||
ParamDefinition.resolveValueRefs(definition, transform.params);
|
||||
ParamDefinition.resolveRefs(definition, transform.params, ctx.getCellData);
|
||||
return { definition, values: transform.params };
|
||||
}
|
||||
|
||||
|
||||
@@ -296,6 +296,13 @@ export namespace ParamDefinition {
|
||||
return setInfo<ValueRef<T>>({ type: 'value-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any }, getOptions, resolveRef }, info);
|
||||
}
|
||||
|
||||
export interface DataRef<T = any> extends Base<{ ref: string, getValue: () => T }> {
|
||||
type: 'data-ref'
|
||||
}
|
||||
export function DataRef<T>(info?: Info & { defaultRef?: string }) {
|
||||
return setInfo<DataRef<T>>({ type: 'data-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any } }, info);
|
||||
}
|
||||
|
||||
export interface Converted<T, C> extends Base<T> {
|
||||
type: 'converted',
|
||||
converted: Any,
|
||||
@@ -329,7 +336,7 @@ export namespace ParamDefinition {
|
||||
|
||||
export type Any =
|
||||
| Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Mat4 | Numeric | FileParam | UrlParam | FileListParam | Interval | LineGraph
|
||||
| ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef
|
||||
| ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef | DataRef
|
||||
|
||||
export type Params = { [k: string]: Any }
|
||||
export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }
|
||||
@@ -360,29 +367,33 @@ export namespace ParamDefinition {
|
||||
return () => resolve(ref);
|
||||
}
|
||||
|
||||
function resolveRefValue(p: Any, value: any) {
|
||||
function resolveRefValue(p: Any, value: any, getData: (ref: string) => any) {
|
||||
if (!value) return;
|
||||
|
||||
if (p.type === 'value-ref') {
|
||||
const v = value as ValueRef['defaultValue'];
|
||||
if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); };
|
||||
else v.getValue = _resolveRef(p.resolveRef, v.ref);
|
||||
} else if (p.type === 'data-ref') {
|
||||
const v = value as ValueRef['defaultValue'];
|
||||
if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); };
|
||||
else v.getValue = _resolveRef(getData, v.ref);
|
||||
} else if (p.type === 'group') {
|
||||
resolveValueRefs(p.params, value);
|
||||
resolveRefs(p.params, value, getData);
|
||||
} else if (p.type === 'mapped') {
|
||||
const v = value as NamedParams;
|
||||
const param = p.map(v.name);
|
||||
resolveRefValue(param, v.params);
|
||||
resolveRefValue(param, v.params, getData);
|
||||
} else if (p.type === 'object-list') {
|
||||
if (!hasValueRef(p.element)) return;
|
||||
for (const e of value) {
|
||||
resolveValueRefs(p.element, e);
|
||||
resolveRefs(p.element, e, getData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasParamValueRef(p: Any) {
|
||||
if (p.type === 'value-ref') {
|
||||
if (p.type === 'value-ref' || p.type === 'data-ref') {
|
||||
return true;
|
||||
} else if (p.type === 'group') {
|
||||
if (hasValueRef(p.params)) return true;
|
||||
@@ -403,9 +414,9 @@ export namespace ParamDefinition {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function resolveValueRefs(params: Params, values: any) {
|
||||
export function resolveRefs(params: Params, values: any, getData: (ref: string) => any) {
|
||||
for (const n of Object.keys(params)) {
|
||||
resolveRefValue(params[n], values?.[n]);
|
||||
resolveRefValue(params[n], values?.[n], getData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 0.9.5
|
||||
* Support molstar_global_model_transform_info category.
|
||||
|
||||
# 0.9.4
|
||||
* bug fix for /ligand queries on metal ions
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import { MolEncoder } from '../../../mol-io/writer/mol/encoder';
|
||||
import { Mol2Encoder } from '../../../mol-io/writer/mol2/encoder';
|
||||
import { ComponentAtom } from '../../../mol-model-formats/structure/property/atoms/chem_comp';
|
||||
import { Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { GlobalModelTransformInfo } from '../../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
export interface Stats {
|
||||
structure: StructureWrapper,
|
||||
@@ -247,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
|
||||
|
||||
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
|
||||
if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
|
||||
if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform);
|
||||
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
|
||||
perf.end('encode');
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default '0.9.4';
|
||||
export default '0.9.5';
|
||||
Reference in New Issue
Block a user