mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
83 Commits
v3.18.0
...
clamp-cube
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3667092327 | ||
|
|
842824057b | ||
|
|
6a32f85e60 | ||
|
|
f29c62ec33 | ||
|
|
d77981230b | ||
|
|
e2b92c15f0 | ||
|
|
14614f4803 | ||
|
|
37d3489b07 | ||
|
|
f81225cc0d | ||
|
|
eb47f43940 | ||
|
|
7618a5e2c9 | ||
|
|
ab3ff842b2 | ||
|
|
82f0f92c15 | ||
|
|
545d9434d8 | ||
|
|
bbc43d5113 | ||
|
|
a6709acf65 | ||
|
|
509a027742 | ||
|
|
7244023233 | ||
|
|
c5f987d8b2 | ||
|
|
793696d4c0 | ||
|
|
305ca05f04 | ||
|
|
f4d7d1920a | ||
|
|
458aad0161 | ||
|
|
9e3132461f | ||
|
|
8301291215 | ||
|
|
daed14e228 | ||
|
|
7db82c5ba5 | ||
|
|
91d03c22c2 | ||
|
|
bc188f0d2b | ||
|
|
3981225824 | ||
|
|
1886d9d72f | ||
|
|
2a7dec8892 | ||
|
|
35d4a5b297 | ||
|
|
26345bfa50 | ||
|
|
8c9b8676dd | ||
|
|
5593c7a75f | ||
|
|
5b70c14ffe | ||
|
|
5e4d611044 | ||
|
|
7ab9d57156 | ||
|
|
9ea6f51126 | ||
|
|
649fe4f4f0 | ||
|
|
53df86c585 | ||
|
|
87c708e3c1 | ||
|
|
ba927b0490 | ||
|
|
2a09725c98 | ||
|
|
9fa0d17933 | ||
|
|
8d9f8a996a | ||
|
|
8814b60d0b | ||
|
|
541c07c53a | ||
|
|
6cbed80815 | ||
|
|
a3c1fdc0f4 | ||
|
|
ddf789b01c | ||
|
|
ab86cc0bf3 | ||
|
|
dc8fab5820 | ||
|
|
813c4f845a | ||
|
|
6ed42e9521 | ||
|
|
fb01ba60ec | ||
|
|
ea4210ded5 | ||
|
|
75e5cf54d6 | ||
|
|
7cebc85a95 | ||
|
|
c00faafa6d | ||
|
|
c9b14f0742 | ||
|
|
9624137c0d | ||
|
|
3eb433368f | ||
|
|
58691f4f5f | ||
|
|
5e9295abd5 | ||
|
|
6ed0ae55b2 | ||
|
|
84448d0aa1 | ||
|
|
31ced24966 | ||
|
|
24681840af | ||
|
|
5d28aa4f2e | ||
|
|
7dabdf3085 | ||
|
|
d7cbd5570c | ||
|
|
80011d4aea | ||
|
|
c6fe440a01 | ||
|
|
ba8d6dc3fa | ||
|
|
378f4f8304 | ||
|
|
0bdcfea276 | ||
|
|
718f76313f | ||
|
|
9f72465052 | ||
|
|
ac33b4a322 | ||
|
|
911c844e54 | ||
|
|
12b9655565 |
10
.github/pull_request_template.md
vendored
Normal file
10
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<!-- Thank you for contributing to Mol* -->
|
||||
|
||||
# Description
|
||||
|
||||
|
||||
## Actions
|
||||
|
||||
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
|
||||
- [ ] Updated headers of modified files
|
||||
- [ ] Added my name to `package.json`'s `contributors`
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -6,7 +6,6 @@
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"firsttris.vscode-jest-runner",
|
||||
"msjsdiag.debugger-for-chrome",
|
||||
"slevesque.shader",
|
||||
"stpn.vscode-graphql",
|
||||
"wayou.vscode-todo-highlight"
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -6,6 +6,41 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Make `PluginContext.initContainer` checkered canvas background optional
|
||||
|
||||
## [v3.23.0] - 2022-10-19
|
||||
|
||||
- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
|
||||
- Add `PluginContext.canvas3dInitialized`
|
||||
- `createPluginUI` now resolves after the 3d canvas has been initialized
|
||||
- Change EM Volume Streaming default from `Whote Structure` to `Auto`
|
||||
|
||||
## [v3.22.0] - 2022-10-17
|
||||
|
||||
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
|
||||
|
||||
## [v3.21.0] - 2022-10-17
|
||||
|
||||
- Add `VolumeIsosurfaceParams.pickingGranularity` param
|
||||
- Prevent component controls collapsing when option is selected
|
||||
|
||||
## [v3.20.0] - 2022-10-16
|
||||
|
||||
- [Breaking] Rename the ``model-index`` color theme to ``trajectory-index``
|
||||
- Add a new ``model-index`` color theme that uniquely colors each loaded model
|
||||
- Add the new ``model-index`` and ``structure-index`` color themes as an option for the carbon color in the ``element-symbol`` and ``ilustrative`` color themes
|
||||
- Add ``structure-index`` color theme that uniquely colors each root structure
|
||||
- Add ``nearest`` method to ``Lookup3D``
|
||||
- Add mipmap-based blur for skybox backgrounds
|
||||
|
||||
## [v3.19.0] - 2022-10-01
|
||||
|
||||
- Fix "empty textures" error on empty canvas
|
||||
- Optimize BinaryCIF integer packing encoder
|
||||
- Fix dual depth peeling when post-processing is off or when rendering direct-volumes
|
||||
- Add ``cameraClipping.minNear`` parameter
|
||||
- Fix black artifacts on specular highlights with transparent background
|
||||
|
||||
## [v3.18.0] - 2022-09-17
|
||||
|
||||
- Integration of Dual depth peeling - OIT method
|
||||
|
||||
2450
package-lock.json
generated
2450
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.18.0",
|
||||
"version": "3.23.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -97,43 +97,43 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.2.1",
|
||||
"@graphql-codegen/cli": "^2.12.0",
|
||||
"@graphql-codegen/cli": "^2.13.7",
|
||||
"@graphql-codegen/time": "^3.2.1",
|
||||
"@graphql-codegen/typescript": "^2.7.3",
|
||||
"@graphql-codegen/typescript": "^2.7.4",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.4",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.6",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.4",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.1",
|
||||
"@types/jest": "^29.0.3",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
"@typescript-eslint/parser": "^5.40.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^7.4.0",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint": "^8.25.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^29.0.3",
|
||||
"jest": "^29.2.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.54.9",
|
||||
"sass-loader": "^13.0.2",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^13.1.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^29.0.1",
|
||||
"typescript": "^4.8.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.8.4",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
@@ -142,20 +142,20 @@
|
||||
"@types/benchmark": "^2.1.2",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^16.11.59",
|
||||
"@types/node": "^16.11.66",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.20.0",
|
||||
"body-parser": "^1.20.1",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.1",
|
||||
"express": "^4.18.2",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.15",
|
||||
"immutable": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.6",
|
||||
"swagger-ui-dist": "^4.14.0",
|
||||
"rxjs": "^7.5.7",
|
||||
"swagger-ui-dist": "^4.14.3",
|
||||
"tslib": "^2.4.0",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
|
||||
@@ -58,20 +58,22 @@ class Viewer {
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = { ...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
const o = {
|
||||
...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowControls: false,
|
||||
viewportShowSettings: false,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
} };
|
||||
viewportShowExpand: true,
|
||||
viewportShowControls: false,
|
||||
viewportShowSettings: false,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
}
|
||||
};
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
@@ -135,18 +137,16 @@ class Viewer {
|
||||
}
|
||||
};
|
||||
|
||||
plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
if (v) {
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
} });
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, {
|
||||
settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'ec-type':
|
||||
case 'ucode-alphanum-csv':
|
||||
case 'id_list':
|
||||
case 'entity_id_list':
|
||||
return ListCol('str', ',', description);
|
||||
case 'id_list_spc':
|
||||
return ListCol('str', ' ', description);
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { AlphaOrbitalsExample } from '.';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
|
||||
|
||||
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
|
||||
createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
|
||||
<Controls orbitals={orbitals} />
|
||||
</PluginContextContainer>, parent);
|
||||
</PluginContextContainer>);
|
||||
}
|
||||
|
||||
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
|
||||
|
||||
@@ -82,24 +82,20 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
|
||||
if (!init) return;
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
});
|
||||
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
|
||||
@@ -72,6 +72,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
|
||||
lightness: 0,
|
||||
saturation: 0,
|
||||
opacity: 1,
|
||||
blur: 0.3,
|
||||
}
|
||||
}
|
||||
}, 'Purple Nebula Skybox'],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -260,7 +260,8 @@ namespace Camera {
|
||||
radius: 0,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
clipFar: true,
|
||||
minNear: 5,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -276,6 +277,7 @@ namespace Camera {
|
||||
radiusMax: number
|
||||
fog: number
|
||||
clipFar: boolean
|
||||
minNear: number
|
||||
}
|
||||
|
||||
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
|
||||
@@ -292,6 +294,7 @@ namespace Camera {
|
||||
if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
|
||||
if (typeof source.fog !== 'undefined') out.fog = source.fog;
|
||||
if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
|
||||
if (typeof source.minNear !== 'undefined') out.minNear = source.minNear;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -303,6 +306,7 @@ namespace Camera {
|
||||
&& a.radiusMax === b.radiusMax
|
||||
&& a.fog === b.fog
|
||||
&& a.clipFar === b.clipFar
|
||||
&& a.minNear === b.minNear
|
||||
&& Vec3.exactEquals(a.position, b.position)
|
||||
&& Vec3.exactEquals(a.up, b.up)
|
||||
&& Vec3.exactEquals(a.target, b.target);
|
||||
@@ -370,7 +374,7 @@ function updatePers(camera: Camera) {
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state;
|
||||
let { radius, radiusMax, mode, fog, clipFar, minNear } = camera.state;
|
||||
if (radius < 0.01) radius = 0.01;
|
||||
|
||||
const normalizedFar = clipFar ? radius : radiusMax;
|
||||
@@ -384,12 +388,12 @@ function updateClip(camera: Camera) {
|
||||
|
||||
if (mode === 'perspective') {
|
||||
// set at least to 5 to avoid slow sphere impostor rendering
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
near = Math.max(Math.min(radiusMax, minNear), near);
|
||||
far = Math.max(minNear, far);
|
||||
} else {
|
||||
// not too close to 0 as it causes issues with outline rendering
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
near = Math.max(Math.min(radiusMax, minNear), near);
|
||||
far = Math.max(minNear, far);
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
|
||||
@@ -65,6 +65,7 @@ export const Canvas3DParams = {
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
minNear: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }, { description: 'Note, may cause performance issues rendering impostors when set too small and cause issues with outline rendering when too close to 0.' }),
|
||||
}, { pivot: 'radius' }),
|
||||
viewport: PD.MappedStatic('canvas', {
|
||||
canvas: PD.Group({}),
|
||||
@@ -322,6 +323,7 @@ namespace Canvas3D {
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far,
|
||||
minNear: p.cameraClipping.minNear,
|
||||
fov: degToRad(p.camera.fov),
|
||||
}, { x, y, width, height }, { pixelScale: attribs.pixelScale });
|
||||
const stereoCamera = new StereoCamera(camera, p.camera.stereo.params);
|
||||
@@ -687,7 +689,7 @@ namespace Canvas3D {
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraClipping: { far: camera.state.clipFar, radius, minNear: camera.state.minNear },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
sceneRadiusFactor: p.sceneRadiusFactor,
|
||||
transparentBackground: p.transparentBackground,
|
||||
@@ -814,6 +816,9 @@ namespace Canvas3D {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far;
|
||||
}
|
||||
if (props.cameraClipping.minNear !== undefined && props.cameraClipping.minNear !== camera.state.minNear) {
|
||||
cameraState.minNear = props.cameraClipping.minNear;
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (getSceneRadius() / 100) * (100 - props.cameraClipping.radius);
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
|
||||
@@ -49,6 +49,7 @@ const SkyboxParams = {
|
||||
pz: PD.File({ label: 'Positive Z / Front', accept: 'image/*' }),
|
||||
}, { isExpanded: true, label: 'Files' }),
|
||||
}),
|
||||
blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or when "EXT_shader_texture_lod" is available.' }),
|
||||
...SharedParams,
|
||||
};
|
||||
type SkyboxProps = PD.Values<typeof SkyboxParams>
|
||||
@@ -170,6 +171,7 @@ export class BackgroundPass {
|
||||
Mat4.invert(m, m);
|
||||
ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m);
|
||||
|
||||
ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness);
|
||||
@@ -367,7 +369,7 @@ function getSkyboxTexture(ctx: WebGLContext, assetManager: AssetManager, faces:
|
||||
const cubeAssets = getCubeAssets(assetManager, faces);
|
||||
const cubeFaces = getCubeFaces(assetManager, cubeAssets);
|
||||
const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz];
|
||||
const texture = ctx.resources.cubeTexture(cubeFaces, false, onload);
|
||||
const texture = ctx.resources.cubeTexture(cubeFaces, true, onload);
|
||||
return { texture, assets };
|
||||
}
|
||||
|
||||
@@ -424,12 +426,15 @@ const BackgroundSchema = {
|
||||
uGradientColorA: UniformSpec('v3'),
|
||||
uGradientColorB: UniformSpec('v3'),
|
||||
uGradientRatio: UniformSpec('f'),
|
||||
uBlur: UniformSpec('f'),
|
||||
uOpacity: UniformSpec('f'),
|
||||
uSaturation: UniformSpec('f'),
|
||||
uLightness: UniformSpec('f'),
|
||||
dVariant: DefineSpec('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']),
|
||||
};
|
||||
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag);
|
||||
const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag, {
|
||||
shaderTextureLod: 'optional'
|
||||
});
|
||||
type BackgroundRenderable = ComputeRenderable<Values<typeof BackgroundSchema>>
|
||||
|
||||
function getBackgroundRenderable(ctx: WebGLContext, width: number, height: number): BackgroundRenderable {
|
||||
@@ -448,6 +453,7 @@ function getBackgroundRenderable(ctx: WebGLContext, width: number, height: numbe
|
||||
uGradientColorA: ValueCell.create(Vec3()),
|
||||
uGradientColorB: ValueCell.create(Vec3()),
|
||||
uGradientRatio: ValueCell.create(0.5),
|
||||
uBlur: ValueCell.create(0),
|
||||
uOpacity: ValueCell.create(1),
|
||||
uSaturation: ValueCell.create(0),
|
||||
uLightness: ValueCell.create(0),
|
||||
|
||||
@@ -153,6 +153,8 @@ export class DrawPass {
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
|
||||
}
|
||||
|
||||
this.depthTextureOpaque.detachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
|
||||
// render transparent primitives
|
||||
if (scene.opacityAverage < 1) {
|
||||
const target = PostprocessingPass.isEnabled(postprocessingProps)
|
||||
|
||||
@@ -24,7 +24,10 @@ export class Passes {
|
||||
|
||||
updateSize() {
|
||||
const { gl } = this.webgl;
|
||||
this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
// Avoid setting dimensions to 0x0 because it causes "empty textures are not allowed" error.
|
||||
const width = Math.max(gl.drawingBufferWidth, 2);
|
||||
const height = Math.max(gl.drawingBufferHeight, 2);
|
||||
this.draw.setSize(width, height);
|
||||
this.pick.syncSize();
|
||||
this.multiSample.syncSize();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,7 +11,7 @@ import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { createTexture, loadImageTexture, Texture } from '../../mol-gl/webgl/texture';
|
||||
import { loadImageTexture, Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Vec2, Vec4 } from '../../mol-math/linear-algebra';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
@@ -74,6 +74,7 @@ export class SmaaPass {
|
||||
state.viewport(x, y, width, height);
|
||||
state.scissor(x, y, width, height);
|
||||
|
||||
state.colorMask(true, true, true, true);
|
||||
state.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
@@ -191,8 +192,8 @@ function getWeightsRenderable(ctx: WebGLContext, edgesTexture: Texture): Weights
|
||||
const width = edgesTexture.getWidth();
|
||||
const height = edgesTexture.getHeight();
|
||||
|
||||
const areaTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgb', 'ubyte', 'linear');
|
||||
const searchTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
const areaTexture = ctx.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
|
||||
const searchTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
|
||||
const values: Values<typeof WeightsSchema> = {
|
||||
...QuadValues,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -88,7 +88,7 @@ export class FontAtlas {
|
||||
this.scratchCanvas.width = this.maxWidth;
|
||||
this.scratchCanvas.height = this.lineHeight;
|
||||
|
||||
this.scratchContext = this.scratchCanvas.getContext('2d')!;
|
||||
this.scratchContext = this.scratchCanvas.getContext('2d', { willReadFrequently: true })!;
|
||||
this.scratchContext.font = `${p.fontStyle} ${p.fontVariant} ${p.fontWeight} ${fontSize}px ${p.fontFamily}`;
|
||||
this.scratchContext.fillStyle = 'black';
|
||||
this.scratchContext.textBaseline = 'middle';
|
||||
|
||||
@@ -387,6 +387,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
|
||||
const type = isInstanceType ? 'volumeInstance' : 'volume';
|
||||
if (isTimingMode) webgl.timer.markEnd('calcTextureMeshColorSmoothing');
|
||||
|
||||
// printTextureImage(readTexture(webgl, texture), { scale: 0.75 });
|
||||
|
||||
return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
gl.finish();
|
||||
if (isTimingMode) ctx.timer.markEnd('createHistogramPyramid');
|
||||
|
||||
// printTexture(ctx, pyramidTex, 2)
|
||||
// printTextureImage(readTexture(ctx, pyramidTex), { scale: 0.75 });
|
||||
|
||||
//
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
|
||||
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim);
|
||||
// console.log('volumeData', volumeData);
|
||||
// console.log('at', readTexture(ctx, activeVoxelsTex));
|
||||
// printTextureImage(readTexture(ctx, activeVoxelsTex), { scale: 0.75 });
|
||||
|
||||
gl.finish();
|
||||
if (isTimingMode) ctx.timer.markEnd('calcActiveVoxels');
|
||||
|
||||
@@ -199,6 +199,10 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
gl.finish();
|
||||
if (isTimingMode) ctx.timer.markEnd('createIsosurfaceBuffers');
|
||||
|
||||
// printTextureImage(readTexture(ctx, vertexTexture, new Float32Array(width * height * 4)), { scale: 0.75 });
|
||||
// printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75 });
|
||||
// printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75 });
|
||||
|
||||
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
|
||||
}
|
||||
|
||||
|
||||
@@ -75,9 +75,9 @@ export function getSharedCopyRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
const ReadTextureName = 'read-texture';
|
||||
const ReadAlphaTextureName = 'read-alpha-texture';
|
||||
|
||||
export function readTexture(ctx: WebGLContext, texture: Texture) {
|
||||
export function readTexture<T extends Uint8Array | Float32Array | Int32Array = Uint8Array>(ctx: WebGLContext, texture: Texture, array?: T) {
|
||||
const { gl, resources } = ctx;
|
||||
if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
|
||||
if (!array && texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
|
||||
|
||||
if (!ctx.namedFramebuffers[ReadTextureName]) {
|
||||
ctx.namedFramebuffers[ReadTextureName] = resources.framebuffer();
|
||||
@@ -86,7 +86,7 @@ export function readTexture(ctx: WebGLContext, texture: Texture) {
|
||||
|
||||
const width = texture.getWidth();
|
||||
const height = texture.getHeight();
|
||||
const array = new Uint8Array(width * height * 4);
|
||||
if (!array) array = new Uint8Array(width * height * 4) as T;
|
||||
framebuffer.bind();
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
ctx.readPixels(0, 0, width, height, array);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -8,6 +8,7 @@ import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { TextureFilter } from '../webgl/texture';
|
||||
import { arrayMinMax } from '../../mol-util/array';
|
||||
|
||||
export function calculateTextureInfo(n: number, itemSize: number) {
|
||||
n = Math.max(n, 2); // observed issues with 1 pixel textures
|
||||
@@ -42,7 +43,8 @@ export function createTextureImage<T extends Uint8Array | Float32Array>(n: numbe
|
||||
const DefaultPrintImageOptions = {
|
||||
scale: 1,
|
||||
pixelated: false,
|
||||
id: 'molstar.debug.image'
|
||||
id: 'molstar.debug.image',
|
||||
normalize: false,
|
||||
};
|
||||
export type PrintImageOptions = typeof DefaultPrintImageOptions
|
||||
|
||||
@@ -58,7 +60,17 @@ export function printTextureImage(textureImage: TextureImage<any>, options: Part
|
||||
}
|
||||
}
|
||||
} else if (itemSize === 4) {
|
||||
data.set(array);
|
||||
if (options.normalize) {
|
||||
const [min, max] = arrayMinMax(array);
|
||||
for (let i = 0, il = width * height * 4; i < il; i += 4) {
|
||||
data[i] = ((array[i] - min) / (max - min)) * 255;
|
||||
data[i + 1] = ((array[i + 1] - min) / (max - min)) * 255;
|
||||
data[i + 2] = ((array[i + 2] - min) / (max - min)) * 255;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
} else {
|
||||
data.set(array);
|
||||
}
|
||||
} else {
|
||||
console.warn(`itemSize '${itemSize}' not supported`);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ precision mediump sampler2D;
|
||||
#if defined(dVariant_skybox)
|
||||
uniform samplerCube tSkybox;
|
||||
uniform mat4 uViewDirectionProjectionInverse;
|
||||
uniform float uBlur;
|
||||
uniform float uOpacity;
|
||||
uniform float uSaturation;
|
||||
uniform float uLightness;
|
||||
@@ -49,7 +50,11 @@ vec3 lightenColor(vec3 c, float amount) {
|
||||
void main() {
|
||||
#if defined(dVariant_skybox)
|
||||
vec4 t = uViewDirectionProjectionInverse * vPosition;
|
||||
gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w));
|
||||
#ifdef enabledShaderTextureLod
|
||||
gl_FragColor = textureCubeLodEXT(tSkybox, normalize(t.xyz / t.w), uBlur * 8.0);
|
||||
#else
|
||||
gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w));
|
||||
#endif
|
||||
gl_FragColor.a = uOpacity;
|
||||
gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness);
|
||||
#elif defined(dVariant_image)
|
||||
|
||||
@@ -57,6 +57,7 @@ export const apply_light_color = `
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
outgoingLight = clamp(outgoingLight, 0.01, 0.99); // prevents black artifacts on specular highlight with transparent background
|
||||
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
#endif
|
||||
|
||||
@@ -37,7 +37,7 @@ export const dpoit_write = `
|
||||
// back color is separately blend afterwards each pass
|
||||
gl_FragData[1] = vec4(0.0);
|
||||
|
||||
float nearestDepth = - lastDepth.x;
|
||||
float nearestDepth = -lastDepth.x;
|
||||
float furthestDepth = lastDepth.y;
|
||||
float alphaMultiplier = 1.0 - lastFrontColor.a;
|
||||
|
||||
|
||||
@@ -264,28 +264,35 @@ export namespace ArrayEncoding {
|
||||
return false;
|
||||
}
|
||||
|
||||
function packingSize(data: Int32Array, upperLimit: number) {
|
||||
function packingSizeUnsigned(data: Int32Array, upperLimit: number) {
|
||||
let size = 0;
|
||||
for (let i = 0, n = data.length; i < n; i++) {
|
||||
size += (data[i] / upperLimit) | 0;
|
||||
}
|
||||
size += data.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
function packingSizeSigned(data: Int32Array, upperLimit: number) {
|
||||
const lowerLimit = -upperLimit - 1;
|
||||
let size = 0;
|
||||
for (let i = 0, n = data.length; i < n; i++) {
|
||||
const value = data[i];
|
||||
if (value === 0) {
|
||||
size += 1;
|
||||
} else if (value > 0) {
|
||||
size += Math.ceil(value / upperLimit);
|
||||
if (value % upperLimit === 0) size += 1;
|
||||
if (value >= 0) {
|
||||
size += (value / upperLimit) | 0;
|
||||
} else {
|
||||
size += Math.ceil(value / lowerLimit);
|
||||
if (value % lowerLimit === 0) size += 1;
|
||||
size += (value / lowerLimit) | 0;
|
||||
}
|
||||
}
|
||||
size += data.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
function determinePacking(data: Int32Array): { isSigned: boolean, size: number, bytesPerElement: number } {
|
||||
const signed = isSigned(data);
|
||||
const size8 = signed ? packingSize(data, 0x7F) : packingSize(data, 0xFF);
|
||||
const size16 = signed ? packingSize(data, 0x7FFF) : packingSize(data, 0xFFFF);
|
||||
const size8 = signed ? packingSizeSigned(data, 0x7F) : packingSizeUnsigned(data, 0xFF);
|
||||
const size16 = signed ? packingSizeSigned(data, 0x7FFF) : packingSizeUnsigned(data, 0xFFFF);
|
||||
|
||||
if (data.length * 4 < size16 * 2) {
|
||||
// 4 byte packing is the most effective
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.360, IHM 1.17, MA 1.4.2.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { GridLookup3D } from '../../geometry';
|
||||
@@ -24,9 +25,17 @@ describe('GridLookup3d', () => {
|
||||
expect(r.count).toBe(1);
|
||||
expect(r.indices[0]).toBe(0);
|
||||
|
||||
r = grid.nearest(0, 0, 0, 1);
|
||||
expect(r.count).toBe(1);
|
||||
expect(r.indices[0]).toBe(0);
|
||||
|
||||
r = grid.find(0, 0, 0, 1);
|
||||
expect(r.count).toBe(3);
|
||||
expect(sortArray(r.indices)).toEqual([0, 1, 2]);
|
||||
|
||||
r = grid.nearest(0, 0, 0, 3);
|
||||
expect(r.count).toBe(3);
|
||||
expect(sortArray(r.indices)).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
it('radius', () => {
|
||||
@@ -38,9 +47,17 @@ describe('GridLookup3d', () => {
|
||||
expect(r.count).toBe(1);
|
||||
expect(r.indices[0]).toBe(0);
|
||||
|
||||
r = grid.nearest(0, 0, 0, 1);
|
||||
expect(r.count).toBe(1);
|
||||
expect(r.indices[0]).toBe(0);
|
||||
|
||||
r = grid.find(0, 0, 0, 0.5);
|
||||
expect(r.count).toBe(2);
|
||||
expect(sortArray(r.indices)).toEqual([0, 1]);
|
||||
|
||||
r = grid.nearest(0, 0, 0, 3);
|
||||
expect(r.count).toBe(3);
|
||||
expect(sortArray(r.indices)).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
it('indexed', () => {
|
||||
@@ -51,8 +68,15 @@ describe('GridLookup3d', () => {
|
||||
let r = grid.find(0, 0, 0, 0);
|
||||
expect(r.count).toBe(0);
|
||||
|
||||
r = grid.nearest(0, 0, 0, 1);
|
||||
expect(r.count).toBe(1);
|
||||
|
||||
r = grid.find(0, 0, 0, 0.5);
|
||||
expect(r.count).toBe(1);
|
||||
expect(sortArray(r.indices)).toEqual([0]);
|
||||
|
||||
r = grid.nearest(0, 0, 0, 3);
|
||||
expect(r.count).toBe(1);
|
||||
expect(sortArray(r.indices)).toEqual([0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,7 +204,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
render(texture, false);
|
||||
}
|
||||
|
||||
// printTexture(webgl, minDistTex, 0.75);
|
||||
// printTextureImage(readTexture(webgl, minDistTex), { scale: 0.75 });
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
@@ -41,8 +41,9 @@ export namespace Result {
|
||||
export interface Lookup3D<T = number> {
|
||||
// The result is mutated with each call to find.
|
||||
find(x: number, y: number, z: number, radius: number, result?: Result<T>): Result<T>,
|
||||
nearest(x: number, y: number, z: number, k: number, stopIf?: Function, result?: Result<T>): Result<T>,
|
||||
check(x: number, y: number, z: number, radius: number): boolean,
|
||||
readonly boundary: { readonly box: Box3D, readonly sphere: Sphere3D }
|
||||
/** transient result */
|
||||
readonly result: Result<T>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { Result, Lookup3D } from './common';
|
||||
@@ -12,6 +13,7 @@ import { PositionData } from '../common';
|
||||
import { Vec3 } from '../../linear-algebra';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { Boundary } from '../boundary';
|
||||
import { FibonacciHeap } from '../../../mol-util/fibonacci-heap';
|
||||
|
||||
interface GridLookup3D<T = number> extends Lookup3D<T> {
|
||||
readonly buckets: { readonly offset: ArrayLike<number>, readonly count: ArrayLike<number>, readonly array: ArrayLike<number> }
|
||||
@@ -40,6 +42,17 @@ class GridLookup3DImpl<T extends number = number> implements GridLookup3D<T> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
nearest(x: number, y: number, z: number, k: number = 1, stopIf?: Function, result?: Result<T>): Result<T> {
|
||||
this.ctx.x = x;
|
||||
this.ctx.y = y;
|
||||
this.ctx.z = z;
|
||||
this.ctx.k = k;
|
||||
this.ctx.stopIf = stopIf;
|
||||
const ret = result ?? this.result;
|
||||
queryNearest(this.ctx, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
check(x: number, y: number, z: number, radius: number): boolean {
|
||||
this.ctx.x = x;
|
||||
this.ctx.y = y;
|
||||
@@ -221,12 +234,14 @@ interface QueryContext {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
k: number,
|
||||
stopIf?: Function,
|
||||
radius: number,
|
||||
isCheck: boolean
|
||||
}
|
||||
|
||||
function createContext(grid: Grid3D): QueryContext {
|
||||
return { grid, x: 0.1, y: 0.1, z: 0.1, radius: 0.1, isCheck: false };
|
||||
return { grid, x: 0.1, y: 0.1, z: 0.1, k: 1, stopIf: undefined, radius: 0.1, isCheck: false };
|
||||
}
|
||||
|
||||
function query<T extends number = number>(ctx: QueryContext, result: Result<T>): boolean {
|
||||
@@ -277,4 +292,152 @@ function query<T extends number = number>(ctx: QueryContext, result: Result<T>):
|
||||
}
|
||||
}
|
||||
return result.count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
const tmpDirVec = Vec3();
|
||||
const tmpVec = Vec3();
|
||||
const tmpSetG = new Set<number>();
|
||||
const tmpSetG2 = new Set<number>();
|
||||
const tmpArrG1 = [0.1];
|
||||
const tmpArrG2 = [0.1];
|
||||
const tmpArrG3 = [0.1];
|
||||
const tmpHeapG = new FibonacciHeap();
|
||||
function queryNearest<T extends number = number>(ctx: QueryContext, result: Result<T>): boolean {
|
||||
const { min, expandedBox: box, boundingSphere: { center }, size: [sX, sY, sZ], bucketOffset, bucketCounts, bucketArray, grid, data: { x: px, y: py, z: pz, indices, radius }, delta, maxRadius } = ctx.grid;
|
||||
const { x, y, z, k, stopIf } = ctx;
|
||||
const indicesCount = OrderedSet.size(indices);
|
||||
Result.reset(result);
|
||||
if (indicesCount === 0 || k <= 0) return false;
|
||||
let gX, gY, gZ, stop = false, gCount = 1, expandGrid = true, nextGCount = 0, arrG = tmpArrG1, nextArrG = tmpArrG2, maxRange = 0, expandRange = true, gridId: number, gridPointsFinished = false;
|
||||
const expandedArrG = tmpArrG3, sqMaxRadius = maxRadius * maxRadius;
|
||||
arrG.length = 0;
|
||||
expandedArrG.length = 0;
|
||||
tmpSetG.clear();
|
||||
tmpHeapG.clear();
|
||||
Vec3.set(tmpVec, x, y, z);
|
||||
if (!Box3D.containsVec3(box, tmpVec)) {
|
||||
// intersect ray pointing to box center
|
||||
Box3D.nearestIntersectionWithRay(tmpVec, box, tmpVec, Vec3.normalize(tmpDirVec, Vec3.sub(tmpDirVec, center, tmpVec)));
|
||||
gX = Math.max(0, Math.min(sX - 1, Math.floor((tmpVec[0] - min[0]) / delta[0])));
|
||||
gY = Math.max(0, Math.min(sY - 1, Math.floor((tmpVec[1] - min[1]) / delta[1])));
|
||||
gZ = Math.max(0, Math.min(sZ - 1, Math.floor((tmpVec[2] - min[2]) / delta[2])));
|
||||
} else {
|
||||
gX = Math.floor((x - min[0]) / delta[0]);
|
||||
gY = Math.floor((y - min[1]) / delta[1]);
|
||||
gZ = Math.floor((z - min[2]) / delta[2]);
|
||||
}
|
||||
const dX = maxRadius !== 0 ? Math.max(1, Math.min(sX - 1, Math.ceil(maxRadius / delta[0]))) : 1;
|
||||
const dY = maxRadius !== 0 ? Math.max(1, Math.min(sY - 1, Math.ceil(maxRadius / delta[1]))) : 1;
|
||||
const dZ = maxRadius !== 0 ? Math.max(1, Math.min(sZ - 1, Math.ceil(maxRadius / delta[2]))) : 1;
|
||||
arrG.push(gX, gY, gZ, (((gX * sY) + gY) * sZ) + gZ);
|
||||
while (result.count < indicesCount) {
|
||||
const arrGLen = gCount * 4;
|
||||
for (let ig = 0; ig < arrGLen; ig += 4) {
|
||||
gridId = arrG[ig + 3];
|
||||
if (!tmpSetG.has(gridId)) {
|
||||
tmpSetG.add(gridId);
|
||||
gridPointsFinished = tmpSetG.size >= grid.length;
|
||||
const bucketIdx = grid[gridId];
|
||||
if (bucketIdx !== 0) {
|
||||
const _maxRange = maxRange;
|
||||
const ki = bucketIdx - 1;
|
||||
const offset = bucketOffset[ki];
|
||||
const count = bucketCounts[ki];
|
||||
const end = offset + count;
|
||||
for (let i = offset; i < end; i++) {
|
||||
const bIdx = bucketArray[i];
|
||||
const idx = OrderedSet.getAt(indices, bIdx);
|
||||
const dx = px[idx] - x;
|
||||
const dy = py[idx] - y;
|
||||
const dz = pz[idx] - z;
|
||||
let distSq = dx * dx + dy * dy + dz * dz;
|
||||
if (maxRadius !== 0) {
|
||||
const r = radius![idx];
|
||||
distSq -= r * r;
|
||||
}
|
||||
if (expandRange && distSq > maxRange) {
|
||||
maxRange = distSq;
|
||||
}
|
||||
tmpHeapG.insert(distSq, bIdx);
|
||||
}
|
||||
if (_maxRange < maxRange) expandRange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// find next grid points
|
||||
nextArrG.length = 0;
|
||||
nextGCount = 0;
|
||||
tmpSetG2.clear();
|
||||
for (let ig = 0; ig < arrGLen; ig += 4) {
|
||||
gX = arrG[ig];
|
||||
gY = arrG[ig + 1];
|
||||
gZ = arrG[ig + 2];
|
||||
// fill grid points array with valid adiacent positions
|
||||
for (let ix = -dX; ix <= dX; ix++) {
|
||||
const xPos = gX + ix;
|
||||
if (xPos < 0 || xPos >= sX) continue;
|
||||
for (let iy = -dY; iy <= dY; iy++) {
|
||||
const yPos = gY + iy;
|
||||
if (yPos < 0 || yPos >= sY) continue;
|
||||
for (let iz = -dZ; iz <= dZ; iz++) {
|
||||
const zPos = gZ + iz;
|
||||
if (zPos < 0 || zPos >= sZ) continue;
|
||||
gridId = (((xPos * sY) + yPos) * sZ) + zPos;
|
||||
if (tmpSetG2.has(gridId)) continue; // already scanned
|
||||
tmpSetG2.add(gridId);
|
||||
if (tmpSetG.has(gridId)) continue; // already visited
|
||||
if (!expandGrid) {
|
||||
const xP = min[0] + xPos * delta[0] - x;
|
||||
const yP = min[1] + yPos * delta[1] - y;
|
||||
const zP = min[2] + zPos * delta[2] - z;
|
||||
const distSqG = (xP * xP) + (yP * yP) + (zP * zP) - sqMaxRadius; // is sqMaxRadius necessary?
|
||||
if (distSqG > maxRange) {
|
||||
expandedArrG.push(xPos, yPos, zPos, gridId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
nextArrG.push(xPos, yPos, zPos, gridId);
|
||||
nextGCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expandGrid = false;
|
||||
if (nextGCount === 0) {
|
||||
if (k === 1) {
|
||||
const node = tmpHeapG.findMinimum();
|
||||
if (node) {
|
||||
const { key: squaredDistance, value: index } = node!;
|
||||
// const squaredDistance = node!.key, index = node!.value;
|
||||
Result.add(result, index as number, squaredDistance as number);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
while (!tmpHeapG.isEmpty() && (gridPointsFinished || tmpHeapG.findMinimum()!.key as number <= maxRange) && result.count < k) {
|
||||
const node = tmpHeapG.extractMinimum();
|
||||
const squaredDistance = node!.key, index = node!.value;
|
||||
Result.add(result, index as number, squaredDistance as number);
|
||||
if (stopIf && !stop) {
|
||||
stop = stopIf(index, squaredDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.count >= k || stop || result.count >= indicesCount) return result.count > 0;
|
||||
expandGrid = true;
|
||||
expandRange = true;
|
||||
if (expandedArrG.length > 0) {
|
||||
for (let i = 0, l = expandedArrG.length; i < l; i++) {
|
||||
arrG.push(expandedArrG[i]);
|
||||
}
|
||||
expandedArrG.length = 0;
|
||||
gCount = arrG.length;
|
||||
}
|
||||
} else {
|
||||
const tmp = arrG;
|
||||
arrG = nextArrG;
|
||||
nextArrG = tmp;
|
||||
gCount = nextGCount;
|
||||
}
|
||||
}
|
||||
return result.count > 0;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,48 @@ namespace Box3D {
|
||||
a.max[2] < b.min[2] || a.min[2] > b.max[2]
|
||||
);
|
||||
}
|
||||
|
||||
// const tmpTransformV = Vec3();
|
||||
export function nearestIntersectionWithRay(out: Vec3, box: Box3D, origin: Vec3, dir: Vec3): Vec3 {
|
||||
const [minX, minY, minZ] = box.min;
|
||||
const [maxX, maxY, maxZ] = box.max;
|
||||
const [x, y, z] = origin;
|
||||
const invDirX = 1.0 / dir[0];
|
||||
const invDirY = 1.0 / dir[1];
|
||||
const invDirZ = 1.0 / dir[2];
|
||||
let tmin, tmax, tymin, tymax, tzmin, tzmax;
|
||||
if (invDirX >= 0) {
|
||||
tmin = (minX - x) * invDirX;
|
||||
tmax = (maxX - x) * invDirX;
|
||||
} else {
|
||||
tmin = (maxX - x) * invDirX;
|
||||
tmax = (minX - x) * invDirX;
|
||||
}
|
||||
if (invDirY >= 0) {
|
||||
tymin = (minY - y) * invDirY;
|
||||
tymax = (maxY - y) * invDirY;
|
||||
} else {
|
||||
tymin = (maxY - y) * invDirY;
|
||||
tymax = (minY - y) * invDirY;
|
||||
}
|
||||
if (invDirZ >= 0) {
|
||||
tzmin = (minZ - z) * invDirZ;
|
||||
tzmax = (maxZ - z) * invDirZ;
|
||||
} else {
|
||||
tzmin = (maxZ - z) * invDirZ;
|
||||
tzmax = (minZ - z) * invDirZ;
|
||||
}
|
||||
if (tymin > tmin)
|
||||
tmin = tymin;
|
||||
if (tymax < tmax)
|
||||
tmax = tymax;
|
||||
if (tzmin > tmin)
|
||||
tmin = tzmin;
|
||||
if (tzmax < tmax)
|
||||
tmax = tzmax;
|
||||
Vec3.scale(out, dir, tmin);
|
||||
return Vec3.set(out, out[0] + x, out[1] + y, out[2] + z);
|
||||
}
|
||||
}
|
||||
|
||||
export { Box3D };
|
||||
export { Box3D };
|
||||
|
||||
@@ -277,6 +277,12 @@ namespace Sphere3D {
|
||||
export function distance(a: Sphere3D, b: Sphere3D) {
|
||||
return Vec3.distance(a.center, b.center) - a.radius + b.radius;
|
||||
}
|
||||
|
||||
/** Get the distance of v from sphere. If negative, v is inside sphere */
|
||||
export function distanceToVec(sphere: Sphere3D, v: Vec3): number {
|
||||
const { center, radius } = sphere;
|
||||
return Vec3.distance(v, center) - radius;
|
||||
}
|
||||
}
|
||||
|
||||
export { Sphere3D };
|
||||
export { Sphere3D };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CubeFile } from '../../mol-io/reader/cube/parser';
|
||||
@@ -11,8 +12,9 @@ import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
import { clamp } from '../../mol-math/interpolate';
|
||||
|
||||
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string, entryId?: string }): Task<Volume> {
|
||||
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string, entryId?: string, clamp?: { min: number, max: number } }): Task<Volume> {
|
||||
return Task.create<Volume>('Create Volume', async () => {
|
||||
const { header, values: sourceValues } = source;
|
||||
const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
|
||||
@@ -24,6 +26,7 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
|
||||
// get every nth value from the source values
|
||||
const [h, k, l] = header.dim;
|
||||
const nth = (params?.dataIndex || 0) + 1;
|
||||
const { min, max } = params?.clamp || { min: -Infinity, max: Infinity };
|
||||
|
||||
let o = 0, s = 0;
|
||||
|
||||
@@ -31,7 +34,7 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
|
||||
for (let u = 0; u < h; u++) {
|
||||
for (let v = 0; v < k; v++) {
|
||||
for (let w = 0; w < l; w++) {
|
||||
values[o++] = sourceValues[s];
|
||||
values[o++] = clamp(sourceValues[s], min, max);
|
||||
s += nth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -71,12 +71,12 @@ namespace CustomModelProperty {
|
||||
},
|
||||
ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add),
|
||||
get: (data: Model) => get(data)?.data,
|
||||
set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
|
||||
set: (data: Model, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
|
||||
const property = get(data);
|
||||
const p = PD.merge(builder.defaultParams, property.props, props);
|
||||
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
|
||||
// this invalidates property.value
|
||||
set(data, p, undefined);
|
||||
set(data, p, value);
|
||||
// dispose of assets
|
||||
data.customProperties.assets(builder.descriptor);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ namespace CustomModelProperty {
|
||||
getParams: () => ({ value: PD.Value(defaultValue, { isHidden: true }) }),
|
||||
isApplicable: () => true,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<typeof defaultParams>>) => {
|
||||
return { value: props.value ?? defaultValue };
|
||||
return { ...PD.getDefaultValues(defaultParams), ...props };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -241,7 +241,9 @@ namespace Loci {
|
||||
? Structure.toStructureElementLoci(loci.structure)
|
||||
: ShapeGroup.isLoci(loci)
|
||||
? Shape.Loci(loci.shape)
|
||||
: loci;
|
||||
: Volume.Cell.isLoci(loci)
|
||||
? Volume.Loci(loci.volume)
|
||||
: loci;
|
||||
},
|
||||
'elementInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
|
||||
@@ -213,6 +213,9 @@ export namespace Model {
|
||||
export type Index = number;
|
||||
export const Index = CustomModelProperty.createSimple<Index>('index', 'static');
|
||||
|
||||
export type MaxIndex = number;
|
||||
export const MaxIndex = CustomModelProperty.createSimple<MaxIndex>('max_index', 'static');
|
||||
|
||||
export function getRoot(model: Model) {
|
||||
return model.parent || model;
|
||||
}
|
||||
|
||||
@@ -1357,6 +1357,9 @@ namespace Structure {
|
||||
export type Index = number;
|
||||
export const Index = CustomStructureProperty.createSimple<Index>('index', 'root');
|
||||
|
||||
export type MaxIndex = number;
|
||||
export const MaxIndex = CustomStructureProperty.createSimple<MaxIndex>('max_index', 'root');
|
||||
|
||||
const PrincipalAxesProp = '__PrincipalAxes__';
|
||||
export function getPrincipalAxes(structure: Structure): PrincipalAxes {
|
||||
if (structure.currentPropertyData[PrincipalAxesProp]) return structure.currentPropertyData[PrincipalAxesProp];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { Structure } from '../structure';
|
||||
@@ -13,6 +14,7 @@ import { StructureUniqueSubsetBuilder } from './unique-subset-builder';
|
||||
import { StructureElement } from '../element';
|
||||
import { Unit } from '../unit';
|
||||
import { UnitIndex } from '../element/util';
|
||||
import { FibonacciHeap } from '../../../../mol-util/fibonacci-heap';
|
||||
|
||||
export interface StructureResult extends Result<StructureElement.UnitIndex> {
|
||||
units: Unit[]
|
||||
@@ -54,6 +56,7 @@ export function StructureLookup3DResultContext(): StructureLookup3DResultContext
|
||||
export class StructureLookup3D {
|
||||
private unitLookup: Lookup3D;
|
||||
private pivot = Vec3();
|
||||
private heap = new FibonacciHeap();
|
||||
|
||||
findUnitIndices(x: number, y: number, z: number, radius: number): Result<number> {
|
||||
return this.unitLookup.find(x, y, z, radius);
|
||||
@@ -86,6 +89,54 @@ export class StructureLookup3D {
|
||||
return ctx.result;
|
||||
}
|
||||
|
||||
nearest(x: number, y: number, z: number, k: number = 1, ctx?: StructureLookup3DResultContext): StructureResult {
|
||||
return this._nearest(x, y, z, k, ctx ?? this.findContext);
|
||||
}
|
||||
|
||||
_nearest(x: number, y: number, z: number, k: number, ctx: StructureLookup3DResultContext): StructureResult {
|
||||
const result = ctx.result, heap = this.heap;
|
||||
Result.reset(result);
|
||||
heap.clear();
|
||||
const { units } = this.structure;
|
||||
let elementsCount = 0;
|
||||
const closeUnits = this.unitLookup.nearest(x, y, z, units.length, (uid: number) => (elementsCount += units[uid].elements.length) >= k, ctx.closeUnitsResult); // sort units based on distance to the point
|
||||
if (closeUnits.count === 0) return result;
|
||||
let totalCount = 0, maxDistResult = -Number.MAX_VALUE;
|
||||
for (let t = 0, _t = closeUnits.count; t < _t; t++) {
|
||||
const unitSqDist = closeUnits.squaredDistances[t];
|
||||
if (totalCount >= k && maxDistResult < unitSqDist) break;
|
||||
Vec3.set(this.pivot, x, y, z);
|
||||
const unit = units[closeUnits.indices[t]];
|
||||
if (!unit.conformation.operator.isIdentity) {
|
||||
Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
|
||||
}
|
||||
const unitLookup = unit.lookup3d;
|
||||
const groupResult = unitLookup.nearest(this.pivot[0], this.pivot[1], this.pivot[2], k, void 0, ctx.unitGroupResult);
|
||||
if (groupResult.count === 0) continue;
|
||||
totalCount += groupResult.count;
|
||||
maxDistResult = Math.max(maxDistResult, groupResult.squaredDistances[groupResult.count - 1]);
|
||||
for (let j = 0, _j = groupResult.count; j < _j; j++) {
|
||||
heap.insert(groupResult.squaredDistances[j], { index: groupResult.indices[j], unit: unit });
|
||||
}
|
||||
}
|
||||
if (k === 1) {
|
||||
const node = heap.findMinimum();
|
||||
if (node) {
|
||||
const { key: squaredDistance } = node;
|
||||
const { unit, index } = node.value as { index: UnitIndex, unit: Unit };
|
||||
StructureResult.add(result, unit as Unit, index as UnitIndex, squaredDistance as number);
|
||||
}
|
||||
} else {
|
||||
while (!heap.isEmpty() && result.count < k) {
|
||||
const node = heap.extractMinimum();
|
||||
const { key: squaredDistance } = node!;
|
||||
const { unit, index } = node!.value as { index: UnitIndex, unit: Unit };
|
||||
StructureResult.add(result, unit as Unit, index as UnitIndex, squaredDistance as number);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
findIntoBuilder(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder) {
|
||||
const { units } = this.structure;
|
||||
const closeUnits = this.unitLookup.find(x, y, z, radius);
|
||||
@@ -217,4 +268,4 @@ export class StructureLookup3D {
|
||||
const position = { x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) };
|
||||
this.unitLookup = GridLookup3D(position, boundary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,4 +219,14 @@ export namespace Volume {
|
||||
return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10);
|
||||
}
|
||||
}
|
||||
|
||||
export type PickingGranularity = 'volume' | 'object' | 'voxel';
|
||||
export const PickingGranularity = {
|
||||
set(volume: Volume, granularity: PickingGranularity) {
|
||||
volume._propertyData['__picking_granularity__'] = granularity;
|
||||
},
|
||||
get(volume: Volume): PickingGranularity {
|
||||
return volume._propertyData['__picking_granularity__'] ?? 'voxel';
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -86,7 +86,7 @@ const allModels = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-all-models',
|
||||
display: {
|
||||
name: 'All Models', group: 'Preset',
|
||||
description: 'Shows all models; colored by model-index.'
|
||||
description: 'Shows all models; colored by trajectory-index.'
|
||||
},
|
||||
isApplicable: o => {
|
||||
return o.data.frameCount > 1;
|
||||
@@ -115,7 +115,7 @@ const allModels = TrajectoryHierarchyPresetProvider({
|
||||
|
||||
const quality = structure.obj ? getStructureQuality(structure.obj.data, { elementCountFactor: tr.frameCount }) : 'medium';
|
||||
const representationPreset = params.representationPreset || plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
|
||||
await builder.representation.applyPreset(structureProperties, representationPreset, { theme: { globalName: 'model-index' }, quality });
|
||||
await builder.representation.applyPreset(structureProperties, representationPreset, { theme: { globalName: 'trajectory-index' }, quality });
|
||||
}
|
||||
|
||||
return { models, structures };
|
||||
|
||||
@@ -88,12 +88,24 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
|
||||
return {
|
||||
dataIndex,
|
||||
entryId: PD.Text(''),
|
||||
clamp: PD.MappedStatic('off', {
|
||||
'off': PD.EmptyGroup(),
|
||||
'on': PD.Group({
|
||||
min: PD.Numeric(-1024),
|
||||
max: PD.Numeric(1024),
|
||||
})
|
||||
}, { cycle: true })
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from Cube', async ctx => {
|
||||
const volume = await volumeFromCube(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const volume = await volumeFromCube(a.data, {
|
||||
dataIndex: params.dataIndex,
|
||||
label: a.data.name || a.label,
|
||||
entryId: params.entryId,
|
||||
clamp: params.clamp.name === 'on' ? params.clamp.params : undefined,
|
||||
}).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
|
||||
@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
|
||||
await options.onBeforeUIRender(ctx);
|
||||
}
|
||||
ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
|
||||
try {
|
||||
await ctx.canvas3dInitialized;
|
||||
} catch {
|
||||
// Error reported in UI/console elsewhere.
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -24,14 +24,6 @@ import { BehaviorSubject } from 'rxjs';
|
||||
import { useBehavior } from './hooks/use-behavior';
|
||||
|
||||
export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> {
|
||||
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
|
||||
return <div className={`msp-layout-region msp-layout-${kind}`}>
|
||||
<div className='msp-layout-static'>
|
||||
{element}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <PluginReactContext.Provider value={this.props.plugin}>
|
||||
<Layout />
|
||||
|
||||
@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
|
||||
await options.onBeforeUIRender(ctx);
|
||||
}
|
||||
createRoot(target).render(createElement(Plugin, { plugin: ctx }));
|
||||
try {
|
||||
await ctx.canvas3dInitialized;
|
||||
} catch {
|
||||
// Error reported in UI/console elsewhere.
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -280,7 +280,6 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
|
||||
|
||||
selectAction: ActionMenu.OnSelect = item => {
|
||||
if (!item) return;
|
||||
this.setState({ action: void 0 });
|
||||
(item?.value as any)();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -19,13 +19,14 @@ export interface ViewportCanvasParams {
|
||||
|
||||
parentClassName?: string,
|
||||
parentStyle?: React.CSSProperties,
|
||||
// NOTE: hostClassName/hostStyle no longer in use
|
||||
// TODO: remove in 4.0
|
||||
hostClassName?: string,
|
||||
hostStyle?: React.CSSProperties,
|
||||
}
|
||||
|
||||
export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, ViewportCanvasState> {
|
||||
private container = React.createRef<HTMLDivElement>();
|
||||
private canvas = React.createRef<HTMLCanvasElement>();
|
||||
|
||||
state: ViewportCanvasState = {
|
||||
noWebGl: false,
|
||||
@@ -37,7 +38,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) {
|
||||
if (!this.container.current || !this.plugin.mount(this.container.current!, { checkeredCanvasBackground: true })) {
|
||||
this.setState({ noWebGl: true });
|
||||
return;
|
||||
}
|
||||
@@ -47,7 +48,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
// TODO viewer cleanup
|
||||
this.plugin.unmount();
|
||||
}
|
||||
|
||||
renderMissing() {
|
||||
@@ -70,10 +71,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
|
||||
|
||||
const Logo = this.props.logo;
|
||||
|
||||
return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle}>
|
||||
<div className={this.props.hostClassName || 'msp-viewport-host3d'} style={this.props.hostStyle} ref={this.container}>
|
||||
<canvas ref={this.canvas} />
|
||||
</div>
|
||||
return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle} ref={this.container}>
|
||||
{(this.state.showLogo && Logo) && <Logo />}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ const SimpleSettingsMapping = ParamMapping({
|
||||
canvas.cameraClipping = {
|
||||
radius: s.clipping.radius,
|
||||
far: s.clipping.far,
|
||||
minNear: s.clipping.minNear,
|
||||
};
|
||||
|
||||
props.layout = s.layout;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -52,17 +52,43 @@ export const StructureInfo = PluginBehavior.create({
|
||||
return { auth, label };
|
||||
}
|
||||
|
||||
private setModelMaxIndex() {
|
||||
const value = this.maxModelIndex;
|
||||
const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model));
|
||||
for (const c of cells) {
|
||||
const m = c.obj?.data;
|
||||
if (m) {
|
||||
if (Model.MaxIndex.get(m).value !== value) {
|
||||
Model.MaxIndex.set(m, { value }, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setStructureMaxIndex() {
|
||||
const value = this.maxModelIndex;
|
||||
const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure));
|
||||
for (const c of cells) {
|
||||
const s = c.obj?.data;
|
||||
if (s) {
|
||||
if (Structure.MaxIndex.get(s).value !== value) {
|
||||
Structure.MaxIndex.set(s, { value }, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleModel(model: Model, oldModel?: Model) {
|
||||
if (Model.Index.get(model).value === undefined) {
|
||||
const oldIndex = oldModel && Model.Index.get(oldModel).value;
|
||||
const value = oldIndex ?? (this.maxModelIndex + 1);
|
||||
Model.Index.set(model, { value });
|
||||
Model.Index.set(model, { value }, value);
|
||||
}
|
||||
|
||||
if (Model.AsymIdOffset.get(model).value === undefined) {
|
||||
const oldOffset = oldModel && Model.AsymIdOffset.get(oldModel).value;
|
||||
const value = oldOffset ?? { ...this.asymIdOffset };
|
||||
Model.AsymIdOffset.set(model, { value });
|
||||
Model.AsymIdOffset.set(model, { value }, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +98,7 @@ export const StructureInfo = PluginBehavior.create({
|
||||
|
||||
const oldIndex = oldStructure && Structure.Index.get(oldStructure).value;
|
||||
const value = oldIndex ?? (this.maxStructureIndex + 1);
|
||||
Structure.Index.set(structure, { value });
|
||||
Structure.Index.set(structure, { value }, value);
|
||||
}
|
||||
|
||||
private handle(ref: string, obj: StateObject<any, StateObject.Type<any>>, oldObj?: StateObject<any, StateObject.Type<any>>) {
|
||||
@@ -92,10 +118,14 @@ export const StructureInfo = PluginBehavior.create({
|
||||
register(): void {
|
||||
this.ctx.customModelProperties.register(Model.AsymIdOffset, true);
|
||||
this.ctx.customModelProperties.register(Model.Index, true);
|
||||
this.ctx.customModelProperties.register(Model.MaxIndex, true);
|
||||
this.ctx.customStructureProperties.register(Structure.Index, true);
|
||||
this.ctx.customStructureProperties.register(Structure.MaxIndex, true);
|
||||
|
||||
this.subscribeObservable(this.ctx.state.data.events.object.created, o => {
|
||||
this.handle(o.ref, o.obj);
|
||||
this.setModelMaxIndex();
|
||||
this.setStructureMaxIndex();
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.ctx.state.data.events.object.updated, o => {
|
||||
@@ -106,7 +136,9 @@ export const StructureInfo = PluginBehavior.create({
|
||||
unregister() {
|
||||
this.ctx.customModelProperties.unregister(Model.AsymIdOffset.descriptor.name);
|
||||
this.ctx.customModelProperties.unregister(Model.Index.descriptor.name);
|
||||
this.ctx.customModelProperties.unregister(Model.MaxIndex.descriptor.name);
|
||||
this.ctx.customStructureProperties.unregister(Structure.Index.descriptor.name);
|
||||
this.ctx.customStructureProperties.unregister(Structure.MaxIndex.descriptor.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -80,7 +80,7 @@ export namespace VolumeStreaming {
|
||||
const box = (structure && structure.boundary.box) || Box3D();
|
||||
|
||||
return {
|
||||
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
|
||||
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'auto' : 'selection-box'), {
|
||||
'off': PD.Group<{}>({}),
|
||||
'box': PD.Group({
|
||||
bottomLeft: PD.Vec3(box.min),
|
||||
|
||||
@@ -43,7 +43,7 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
return {
|
||||
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
|
||||
entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
|
||||
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
|
||||
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'auto' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
|
||||
options: PD.Group({
|
||||
serverUrl: PD.Text(plugin.config.get(PluginConfig.VolumeStreaming.DefaultServer) || 'https://ds.litemol.org'),
|
||||
behaviorRef: PD.Text('', { isHidden: true }),
|
||||
|
||||
@@ -35,7 +35,7 @@ import { Representation } from '../mol-repr/representation';
|
||||
import { StructureRepresentationRegistry } from '../mol-repr/structure/registry';
|
||||
import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry';
|
||||
import { StateTransform } from '../mol-state';
|
||||
import { RuntimeContext, Task } from '../mol-task';
|
||||
import { RuntimeContext, Scheduler, Task } from '../mol-task';
|
||||
import { ColorTheme } from '../mol-theme/color';
|
||||
import { SizeTheme } from '../mol-theme/size';
|
||||
import { ThemeRegistryContext } from '../mol-theme/theme';
|
||||
@@ -71,8 +71,10 @@ export class PluginContext {
|
||||
};
|
||||
|
||||
protected subs: Subscription[] = [];
|
||||
private initCanvas3dPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}];
|
||||
|
||||
private disposed = false;
|
||||
private canvasContainer: HTMLDivElement | undefined = void 0;
|
||||
private ev = RxEventHelper.create();
|
||||
|
||||
readonly config = new PluginConfigManager(this.spec.config); // needed to init state
|
||||
@@ -102,10 +104,15 @@ export class PluginContext {
|
||||
leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root')
|
||||
},
|
||||
canvas3d: {
|
||||
// TODO: remove in 4.0?
|
||||
initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1))
|
||||
}
|
||||
} as const;
|
||||
|
||||
readonly canvas3dInitialized = new Promise<void>((res, rej) => {
|
||||
this.initCanvas3dPromiseCallbacks = [res, rej];
|
||||
});
|
||||
|
||||
readonly canvas3dContext: Canvas3DContext | undefined;
|
||||
readonly canvas3d: Canvas3D | undefined;
|
||||
readonly layout = new PluginLayout(this);
|
||||
@@ -186,6 +193,63 @@ export class PluginContext {
|
||||
*/
|
||||
readonly customState: unknown = Object.create(null);
|
||||
|
||||
initContainer(options?: { canvas3dContext?: Canvas3DContext, checkeredCanvasBackground?: boolean }) {
|
||||
if (this.canvasContainer) return true;
|
||||
|
||||
const container = document.createElement('div');
|
||||
Object.assign(container.style, {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
'-webkit-user-select': 'none',
|
||||
'user-select': 'none',
|
||||
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
|
||||
'-webkit-touch-callout': 'none',
|
||||
'touch-action': 'manipulation',
|
||||
});
|
||||
let canvas = options?.canvas3dContext?.canvas;
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
if (options?.checkeredCanvasBackground) {
|
||||
Object.assign(canvas.style, {
|
||||
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
|
||||
'background-size': '60px 60px',
|
||||
'background-position': '0 0, 30px 30px'
|
||||
});
|
||||
}
|
||||
container.appendChild(canvas);
|
||||
}
|
||||
if (!this.initViewer(canvas, container, options?.canvas3dContext)) {
|
||||
return false;
|
||||
}
|
||||
this.canvasContainer = container;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the plugin into the target element (assumes the target has "relative"-like positioninig).
|
||||
* If initContainer wasn't called separately before, initOptions will be passed to it.
|
||||
*/
|
||||
mount(target: HTMLElement, initOptions?: { canvas3dContext?: Canvas3DContext, checkeredCanvasBackground?: boolean }) {
|
||||
if (this.disposed) throw new Error('Cannot mount a disposed context');
|
||||
|
||||
if (!this.initContainer(initOptions)) return false;
|
||||
|
||||
if (this.canvasContainer!.parentElement !== target) {
|
||||
this.canvasContainer!.parentElement?.removeChild(this.canvasContainer!);
|
||||
}
|
||||
|
||||
target.appendChild(this.canvasContainer!);
|
||||
this.handleResize();
|
||||
return true;
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.canvasContainer?.parentElement?.removeChild(this.canvasContainer);
|
||||
}
|
||||
|
||||
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement, canvas3dContext?: Canvas3DContext) {
|
||||
try {
|
||||
this.layout.setRoot(container);
|
||||
@@ -232,10 +296,12 @@ export class PluginContext {
|
||||
|
||||
this.handleResize();
|
||||
|
||||
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[0]());
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.log.error('' + e);
|
||||
console.error(e);
|
||||
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[1](e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -306,6 +372,9 @@ export class PluginContext {
|
||||
objectForEach(this.managers, m => (m as any)?.dispose?.());
|
||||
objectForEach(this.managers.structure, m => (m as any)?.dispose?.());
|
||||
|
||||
this.unmount();
|
||||
this.canvasContainer = undefined;
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,16 @@ function getLoci(volume: Volume, props: VolumeIsosurfaceProps) {
|
||||
|
||||
function getIsosurfaceLoci(pickingId: PickingId, volume: Volume, props: VolumeIsosurfaceProps, id: number) {
|
||||
const { objectId, groupId } = pickingId;
|
||||
|
||||
if (id === objectId) {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
const granularity = Volume.PickingGranularity.get(volume);
|
||||
if (granularity === 'volume') {
|
||||
return Volume.Loci(volume);
|
||||
} else if (granularity === 'object') {
|
||||
return Volume.Isosurface.Loci(volume, props.isoValue);
|
||||
} else {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
}
|
||||
}
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,14 @@ function getLoci(volume: Volume, props: PD.Values<SliceParams>) {
|
||||
function getSliceLoci(pickingId: PickingId, volume: Volume, props: PD.Values<SliceParams>, id: number) {
|
||||
const { objectId, groupId } = pickingId;
|
||||
if (id === objectId) {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
const granularity = Volume.PickingGranularity.get(volume);
|
||||
if (granularity === 'volume') {
|
||||
return Volume.Loci(volume);
|
||||
} if (granularity === 'object') {
|
||||
return getLoci(volume, props);
|
||||
} else {
|
||||
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
|
||||
}
|
||||
}
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import { UncertaintyColorThemeProvider } from './color/uncertainty';
|
||||
import { EntitySourceColorThemeProvider } from './color/entity-source';
|
||||
import { IllustrativeColorThemeProvider } from './color/illustrative';
|
||||
import { HydrophobicityColorThemeProvider } from './color/hydrophobicity';
|
||||
import { ModelIndexColorThemeProvider } from './color/model-index';
|
||||
import { TrajectoryIndexColorThemeProvider } from './color/trajectory-index';
|
||||
import { OccupancyColorThemeProvider } from './color/occupancy';
|
||||
import { OperatorNameColorThemeProvider } from './color/operator-name';
|
||||
import { OperatorHklColorThemeProvider } from './color/operator-hkl';
|
||||
@@ -38,6 +38,8 @@ import { EntityIdColorThemeProvider } from './color/entity-id';
|
||||
import { Texture, TextureFilter } from '../mol-gl/webgl/texture';
|
||||
import { VolumeValueColorThemeProvider } from './color/volume-value';
|
||||
import { Vec3, Vec4 } from '../mol-math/linear-algebra';
|
||||
import { ModelIndexColorThemeProvider } from './color/model-index';
|
||||
import { StructureIndexColorThemeProvider } from './color/structure-index';
|
||||
|
||||
export type LocationColor = (location: Location, isSecondary: boolean) => Color
|
||||
|
||||
@@ -144,6 +146,8 @@ namespace ColorTheme {
|
||||
'secondary-structure': SecondaryStructureColorThemeProvider,
|
||||
'sequence-id': SequenceIdColorThemeProvider,
|
||||
'shape-group': ShapeGroupColorThemeProvider,
|
||||
'structure-index': StructureIndexColorThemeProvider,
|
||||
'trajectory-index': TrajectoryIndexColorThemeProvider,
|
||||
'uncertainty': UncertaintyColorThemeProvider,
|
||||
'unit-index': UnitIndexColorThemeProvider,
|
||||
'uniform': UniformColorThemeProvider,
|
||||
|
||||
@@ -19,6 +19,8 @@ import { OperatorNameColorThemeParams, OperatorNameColorTheme } from './operator
|
||||
import { EntityIdColorTheme, EntityIdColorThemeParams } from './entity-id';
|
||||
import { assertUnreachable } from '../../mol-util/type-helpers';
|
||||
import { EntitySourceColorTheme, EntitySourceColorThemeParams } from './entity-source';
|
||||
import { ModelIndexColorTheme, ModelIndexColorThemeParams } from './model-index';
|
||||
import { StructureIndexColorTheme, StructureIndexColorThemeParams } from './structure-index';
|
||||
|
||||
// from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
|
||||
export const ElementSymbolColors = ColorMap({
|
||||
@@ -35,6 +37,8 @@ export const ElementSymbolColorThemeParams = {
|
||||
'entity-id': PD.Group(EntityIdColorThemeParams),
|
||||
'entity-source': PD.Group(EntitySourceColorThemeParams),
|
||||
'operator-name': PD.Group(OperatorNameColorThemeParams),
|
||||
'model-index': PD.Group(ModelIndexColorThemeParams),
|
||||
'structure-index': PD.Group(StructureIndexColorThemeParams),
|
||||
'element-symbol': PD.EmptyGroup()
|
||||
}, { description: 'Use chain-id coloring for carbon atoms.' }),
|
||||
saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
@@ -46,7 +50,7 @@ export const ElementSymbolColorThemeParams = {
|
||||
};
|
||||
export type ElementSymbolColorThemeParams = typeof ElementSymbolColorThemeParams
|
||||
export function getElementSymbolColorThemeParams(ctx: ThemeDataContext) {
|
||||
return ElementSymbolColorThemeParams; // TODO return copy
|
||||
return PD.clone(ElementSymbolColorThemeParams);
|
||||
}
|
||||
|
||||
export function elementSymbolColor(colorMap: ElementSymbolColors, element: ElementSymbol): Color {
|
||||
@@ -63,8 +67,10 @@ export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values<
|
||||
pcc.name === 'entity-id' ? EntityIdColorTheme(ctx, pcc.params).color :
|
||||
pcc.name === 'entity-source' ? EntitySourceColorTheme(ctx, pcc.params).color :
|
||||
pcc.name === 'operator-name' ? OperatorNameColorTheme(ctx, pcc.params).color :
|
||||
pcc.name === 'element-symbol' ? undefined :
|
||||
assertUnreachable(pcc);
|
||||
pcc.name === 'model-index' ? ModelIndexColorTheme(ctx, pcc.params).color :
|
||||
pcc.name === 'structure-index' ? StructureIndexColorTheme(ctx, pcc.params).color :
|
||||
pcc.name === 'element-symbol' ? undefined :
|
||||
assertUnreachable(pcc);
|
||||
|
||||
function elementColor(element: ElementSymbol, location: Location) {
|
||||
return (carbonColor && element === 'C')
|
||||
|
||||
@@ -17,9 +17,11 @@ import { assertUnreachable } from '../../mol-util/type-helpers';
|
||||
import { EntityIdColorTheme, EntityIdColorThemeParams } from './entity-id';
|
||||
import { MoleculeTypeColorTheme, MoleculeTypeColorThemeParams } from './molecule-type';
|
||||
import { EntitySourceColorTheme, EntitySourceColorThemeParams } from './entity-source';
|
||||
import { ModelIndexColorTheme, ModelIndexColorThemeParams } from './model-index';
|
||||
import { StructureIndexColorTheme, StructureIndexColorThemeParams } from './structure-index';
|
||||
|
||||
const DefaultIllustrativeColor = Color(0xEEEEEE);
|
||||
const Description = `Assigns an illustrative color that gives every chain a color based on the choosen style but with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`;
|
||||
const Description = `Assigns an illustrative color that gives every chain a color based on the chosen style but with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`;
|
||||
|
||||
export const IllustrativeColorThemeParams = {
|
||||
style: PD.MappedStatic('entity-id', {
|
||||
@@ -28,6 +30,8 @@ export const IllustrativeColorThemeParams = {
|
||||
'entity-id': PD.Group(EntityIdColorThemeParams),
|
||||
'entity-source': PD.Group(EntitySourceColorThemeParams),
|
||||
'molecule-type': PD.Group(MoleculeTypeColorThemeParams),
|
||||
'model-index': PD.Group(ModelIndexColorThemeParams),
|
||||
'structure-index': PD.Group(StructureIndexColorThemeParams),
|
||||
}),
|
||||
carbonLightness: PD.Numeric(0.8, { min: -6, max: 6, step: 0.1 })
|
||||
};
|
||||
@@ -44,7 +48,9 @@ export function IllustrativeColorTheme(ctx: ThemeDataContext, props: PD.Values<I
|
||||
props.style.name === 'entity-id' ? EntityIdColorTheme(ctx, props.style.params) :
|
||||
props.style.name === 'entity-source' ? EntitySourceColorTheme(ctx, props.style.params) :
|
||||
props.style.name === 'molecule-type' ? MoleculeTypeColorTheme(ctx, props.style.params) :
|
||||
assertUnreachable(props.style);
|
||||
props.style.name === 'model-index' ? ModelIndexColorTheme(ctx, props.style.params) :
|
||||
props.style.name === 'structure-index' ? StructureIndexColorTheme(ctx, props.style.params) :
|
||||
assertUnreachable(props.style);
|
||||
|
||||
function illustrativeColor(location: Location, typeSymbol: ElementSymbol) {
|
||||
const baseColor = styleColor(location, false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Jason Pattle <jpattle@exscientia.co.uk>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -14,14 +15,14 @@ import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
|
||||
import { TableLegend, ScaleLegend } from '../../mol-util/legend';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every model a unique color based on the position (index) of the model in the list of models in the structure.';
|
||||
const Description = 'Gives every model a unique color based on its index.';
|
||||
|
||||
export const ModelIndexColorThemeParams = {
|
||||
...getPaletteParams({ type: 'colors', colorList: 'purples' }),
|
||||
...getPaletteParams({ type: 'colors', colorList: 'many-distinct' }),
|
||||
};
|
||||
export type ModelIndexColorThemeParams = typeof ModelIndexColorThemeParams
|
||||
export function getModelIndexColorThemeParams(ctx: ThemeDataContext) {
|
||||
return ModelIndexColorThemeParams; // TODO return copy
|
||||
return PD.clone(ModelIndexColorThemeParams);
|
||||
}
|
||||
|
||||
export function ModelIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<ModelIndexColorThemeParams>): ColorTheme<ModelIndexColorThemeParams> {
|
||||
@@ -29,24 +30,17 @@ export function ModelIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<Mod
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
if (ctx.structure) {
|
||||
const { models } = ctx.structure.root;
|
||||
|
||||
let size = 0;
|
||||
for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m)?.size || 0);
|
||||
// max-index is the same for all models
|
||||
const size = (Model.MaxIndex.get(ctx.structure.models[0]).value ?? -1) + 1;
|
||||
|
||||
const palette = getPalette(size, props);
|
||||
legend = palette.legend;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const idx = Model.TrajectoryInfo.get(models[i])?.index || 0;
|
||||
modelColor.set(idx, palette.color(idx));
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
|
||||
return palette.color(Model.Index.get(location.unit.model).value || 0)!;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
|
||||
return palette.color(Model.Index.get(location.aUnit.model).value || 0)!;
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
@@ -71,5 +65,5 @@ export const ModelIndexColorThemeProvider: ColorTheme.Provider<ModelIndexColorTh
|
||||
factory: ModelIndexColorTheme,
|
||||
getParams: getModelIndexColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ModelIndexColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0 && Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0
|
||||
};
|
||||
67
src/mol-theme/color/structure-index.ts
Normal file
67
src/mol-theme/color/structure-index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { StructureElement, Bond, Structure } from '../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
|
||||
import { TableLegend, ScaleLegend } from '../../mol-util/legend';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every structure a unique color based on its index.';
|
||||
|
||||
export const StructureIndexColorThemeParams = {
|
||||
...getPaletteParams({ type: 'colors', colorList: 'many-distinct' }),
|
||||
};
|
||||
export type StructureIndexColorThemeParams = typeof StructureIndexColorThemeParams
|
||||
export function getStructureIndexColorThemeParams(ctx: ThemeDataContext) {
|
||||
return PD.clone(StructureIndexColorThemeParams);
|
||||
}
|
||||
|
||||
export function StructureIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<StructureIndexColorThemeParams>): ColorTheme<StructureIndexColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
if (ctx.structure) {
|
||||
const size = (Structure.MaxIndex.get(ctx.structure).value ?? -1) + 1;
|
||||
|
||||
const palette = getPalette(size, props);
|
||||
legend = palette.legend;
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return palette.color(Structure.Index.get(location.structure).value || 0)!;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return palette.color(Structure.Index.get(location.aStructure).value || 0)!;
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: StructureIndexColorTheme,
|
||||
granularity: 'instance',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const StructureIndexColorThemeProvider: ColorTheme.Provider<StructureIndexColorThemeParams, 'structure-index'> = {
|
||||
name: 'structure-index',
|
||||
label: 'Structure Index',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: StructureIndexColorTheme,
|
||||
getParams: getStructureIndexColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(StructureIndexColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0
|
||||
};
|
||||
75
src/mol-theme/color/trajectory-index.ts
Normal file
75
src/mol-theme/color/trajectory-index.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { StructureElement, Bond, Model } from '../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../theme';
|
||||
import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
|
||||
import { TableLegend, ScaleLegend } from '../../mol-util/legend';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every model (frame) a unique color based on the index in its trajectory.';
|
||||
|
||||
export const TrajectoryIndexColorThemeParams = {
|
||||
...getPaletteParams({ type: 'colors', colorList: 'purples' }),
|
||||
};
|
||||
export type TrajectoryIndexColorThemeParams = typeof TrajectoryIndexColorThemeParams
|
||||
export function getTrajectoryIndexColorThemeParams(ctx: ThemeDataContext) {
|
||||
return PD.clone(TrajectoryIndexColorThemeParams);
|
||||
}
|
||||
|
||||
export function TrajectoryIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<TrajectoryIndexColorThemeParams>): ColorTheme<TrajectoryIndexColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
if (ctx.structure) {
|
||||
const { models } = ctx.structure.root;
|
||||
|
||||
let size = 0;
|
||||
for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m)?.size || 0);
|
||||
|
||||
const palette = getPalette(size, props);
|
||||
legend = palette.legend;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const idx = Model.TrajectoryInfo.get(models[i])?.index || 0;
|
||||
modelColor.set(idx, palette.color(idx));
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: TrajectoryIndexColorTheme,
|
||||
granularity: 'instance',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const TrajectoryIndexColorThemeProvider: ColorTheme.Provider<TrajectoryIndexColorThemeParams, 'trajectory-index'> = {
|
||||
name: 'trajectory-index',
|
||||
label: 'Trajectory Index',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: TrajectoryIndexColorTheme,
|
||||
getParams: getTrajectoryIndexColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(TrajectoryIndexColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0 && Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1
|
||||
};
|
||||
22
src/mol-util/_spec/fibonacci-heap.spec.ts
Normal file
22
src/mol-util/_spec/fibonacci-heap.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { FibonacciHeap } from '../fibonacci-heap';
|
||||
|
||||
describe('fibonacci-heap', () => {
|
||||
it('basic', () => {
|
||||
const heap = new FibonacciHeap();
|
||||
heap.insert(1, 2);
|
||||
heap.insert(4);
|
||||
heap.insert(2);
|
||||
heap.insert(3);
|
||||
expect(heap.size()).toBe(4);
|
||||
const node = heap.extractMinimum();
|
||||
expect(node!.key).toBe(1);
|
||||
expect(node!.value).toBe(2);
|
||||
expect(heap.size()).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import { deepClone } from '../../mol-util/object';
|
||||
import { deepEqual } from '../../mol-util';
|
||||
import { arraySum } from '../../mol-util/array';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from './names';
|
||||
|
||||
export const DistinctColorsParams = {
|
||||
hue: PD.Interval([1, 360], { min: 0, max: 360, step: 1 }),
|
||||
@@ -105,7 +106,8 @@ export function distinctColors(count: number, props: Partial<DistinctColorsProps
|
||||
|
||||
const samples = getSamples(Math.max(p.minSampleCount, count * 5), p);
|
||||
if (samples.length < count) {
|
||||
throw new Error('Not enough samples to generate distinct colors, increase sample count.');
|
||||
console.warn('Not enough samples to generate distinct colors, increase sample count.');
|
||||
return (new Array(count)).fill(ColorNames.lightgrey);
|
||||
}
|
||||
|
||||
const colors: Lab[] = [];
|
||||
|
||||
407
src/mol-util/fibonacci-heap.ts
Normal file
407
src/mol-util/fibonacci-heap.ts
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*
|
||||
* Adapted from https://github.com/gwtw/ts-fibonacci-heap, Copyright (c) 2014 Daniel Imms, MIT
|
||||
*/
|
||||
|
||||
interface INode<K, V> {
|
||||
key: K;
|
||||
value?: V;
|
||||
}
|
||||
|
||||
type CompareFunction<K, V> = (a: INode<K, V>, b: INode<K, V>) => number;
|
||||
|
||||
class Node<K, V> implements INode<K, V> {
|
||||
public key: K;
|
||||
public value: V | undefined;
|
||||
public prev: Node<K, V>;
|
||||
public next: Node<K, V>;
|
||||
public parent: Node<K, V> | null = null;
|
||||
public child: Node<K, V> | null = null;
|
||||
|
||||
public degree: number = 0;
|
||||
public isMarked: boolean = false;
|
||||
|
||||
constructor(key: K, value?: V) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.prev = this;
|
||||
this.next = this;
|
||||
}
|
||||
}
|
||||
|
||||
class NodeListIterator<K, V> {
|
||||
private _index: number;
|
||||
private _items: Node<K, V>[];
|
||||
private _len: number;
|
||||
/**
|
||||
* Creates an Iterator used to simplify the consolidate() method. It works by
|
||||
* making a shallow copy of the nodes in the root list and iterating over the
|
||||
* shallow copy instead of the source as the source will be modified.
|
||||
* @param start A node from the root list.
|
||||
*/
|
||||
constructor(start?: Node<K, V>) {
|
||||
this._index = -1;
|
||||
this._items = [];
|
||||
this._len = 0;
|
||||
if (start) {
|
||||
let current = start, l = 0;
|
||||
do {
|
||||
this._items[l++] = current;
|
||||
current = current.next;
|
||||
} while (start !== current);
|
||||
this._len = l;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether there is a next node in the iterator.
|
||||
*/
|
||||
public hasNext(): boolean {
|
||||
return this._index < this._len - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The next node.
|
||||
*/
|
||||
public next(): Node<K, V> {
|
||||
return this._items[++this._index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Resets iterator to reuse it.
|
||||
*/
|
||||
public reset(start: Node<K, V>) {
|
||||
this._index = -1;
|
||||
this._len = 0;
|
||||
let current = start, l = 0;
|
||||
do {
|
||||
this._items[l++] = current;
|
||||
current = current.next;
|
||||
} while (start !== current);
|
||||
this._len = l;
|
||||
}
|
||||
}
|
||||
|
||||
const tmpIt = new NodeListIterator<any, any>();
|
||||
/**
|
||||
* A Fibonacci heap data structure with a key and optional value.
|
||||
*/
|
||||
export class FibonacciHeap<K, V> {
|
||||
private _minNode: Node<K, V> | null = null;
|
||||
private _nodeCount: number = 0;
|
||||
private _compare: CompareFunction<K, V>;
|
||||
|
||||
constructor(
|
||||
compare?: CompareFunction<K, V>
|
||||
) {
|
||||
this._compare = compare ? compare : this._defaultCompare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the heap's data, making it an empty heap.
|
||||
*/
|
||||
public clear(): void {
|
||||
this._minNode = null;
|
||||
this._nodeCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases a key of a node.
|
||||
* @param node The node to decrease the key of.
|
||||
* @param newKey The new key to assign to the node.
|
||||
*/
|
||||
public decreaseKey(node: Node<K, V>, newKey: K): void {
|
||||
if (!node) {
|
||||
throw new Error('Cannot decrease key of non-existent node');
|
||||
}
|
||||
if (this._compare({ key: newKey }, { key: node.key }) > 0) {
|
||||
throw new Error('New key is larger than old key');
|
||||
}
|
||||
|
||||
node.key = newKey;
|
||||
const parent = node.parent;
|
||||
if (parent && this._compare(node, parent) < 0) {
|
||||
this._cut(node, parent, <Node<K, V>> this._minNode);
|
||||
this._cascadingCut(parent, <Node<K, V>> this._minNode);
|
||||
}
|
||||
if (this._compare(node, <Node<K, V>> this._minNode) < 0) {
|
||||
this._minNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a node.
|
||||
* @param node The node to delete.
|
||||
*/
|
||||
public delete(node: Node<K, V>): void {
|
||||
// This is a special implementation of decreaseKey that sets the argument to
|
||||
// the minimum value. This is necessary to make generic keys work, since there
|
||||
// is no MIN_VALUE constant for generic types.
|
||||
const parent = node.parent;
|
||||
if (parent) {
|
||||
this._cut(node, parent, <Node<K, V>> this._minNode);
|
||||
this._cascadingCut(parent, <Node<K, V>> this._minNode);
|
||||
}
|
||||
this._minNode = node;
|
||||
|
||||
this.extractMinimum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and returns the minimum node from the heap.
|
||||
* @return The heap's minimum node or null if the heap is empty.
|
||||
*/
|
||||
public extractMinimum(): Node<K, V> | null {
|
||||
const extractedMin = this._minNode;
|
||||
if (extractedMin) {
|
||||
// Set parent to null for the minimum's children
|
||||
if (extractedMin.child) {
|
||||
let child = extractedMin.child;
|
||||
do {
|
||||
child.parent = null;
|
||||
child = child.next;
|
||||
} while (child !== extractedMin.child);
|
||||
}
|
||||
|
||||
let nextInRootList = null;
|
||||
if (extractedMin.next !== extractedMin) {
|
||||
nextInRootList = extractedMin.next;
|
||||
}
|
||||
// Remove min from root list
|
||||
this._removeNodeFromList(extractedMin);
|
||||
this._nodeCount--;
|
||||
|
||||
// Merge the children of the minimum node with the root list
|
||||
this._minNode = this._mergeLists(nextInRootList, extractedMin.child);
|
||||
if (this._minNode) {
|
||||
this._minNode = this._consolidate(this._minNode);
|
||||
}
|
||||
}
|
||||
return extractedMin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum node from the heap.
|
||||
* @return The heap's minimum node or null if the heap is empty.
|
||||
*/
|
||||
public findMinimum(): Node<K, V> | null {
|
||||
return this._minNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new key-value pair into the heap.
|
||||
* @param key The key to insert.
|
||||
* @param value The value to insert.
|
||||
* @return node The inserted node.
|
||||
*/
|
||||
public insert(key: K, value?: V): Node<K, V> {
|
||||
const node = new Node(key, value);
|
||||
this._minNode = this._mergeLists(this._minNode, node);
|
||||
this._nodeCount++;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the heap is empty.
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
return this._minNode === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The size of the heap.
|
||||
*/
|
||||
public size(): number {
|
||||
if (this._minNode === null) {
|
||||
return 0;
|
||||
}
|
||||
return this._getNodeListSize(this._minNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins another heap to this heap.
|
||||
* @param other The other heap.
|
||||
*/
|
||||
public union(other: FibonacciHeap<K, V>): void {
|
||||
this._minNode = this._mergeLists(this._minNode, other._minNode);
|
||||
this._nodeCount += other._nodeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two nodes with each other.
|
||||
* @param a The first key to compare.
|
||||
* @param b The second key to compare.
|
||||
* @return -1, 0 or 1 if a < b, a == b or a > b respectively.
|
||||
*/
|
||||
private _defaultCompare(a: INode<K, V>, b: INode<K, V>): number {
|
||||
if (a.key > b.key) {
|
||||
return 1;
|
||||
}
|
||||
if (a.key < b.key) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cut the link between a node and its parent, moving the node to the root list.
|
||||
* @param node The node being cut.
|
||||
* @param parent The parent of the node being cut.
|
||||
* @param minNode The minimum node in the root list.
|
||||
* @return The heap's new minimum node.
|
||||
*/
|
||||
private _cut(node: Node<K, V>, parent: Node<K, V>, minNode: Node<K, V>): Node<K, V> | null {
|
||||
node.parent = null;
|
||||
parent.degree--;
|
||||
if (node.next === node) {
|
||||
parent.child = null;
|
||||
} else {
|
||||
parent.child = node.next;
|
||||
}
|
||||
this._removeNodeFromList(node);
|
||||
const newMinNode = this._mergeLists(minNode, node);
|
||||
node.isMarked = false;
|
||||
return newMinNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a cascading cut on a node; mark the node if it is not marked,
|
||||
* otherwise cut the node and perform a cascading cut on its parent.
|
||||
* @param node The node being considered to be cut.
|
||||
* @param minNode The minimum node in the root list.
|
||||
* @return The heap's new minimum node.
|
||||
*/
|
||||
private _cascadingCut(node: Node<K, V>, minNode: Node<K, V> | null): Node<K, V> | null {
|
||||
const parent = node.parent;
|
||||
if (parent) {
|
||||
if (node.isMarked) {
|
||||
minNode = this._cut(node, parent, <Node<K, V>>minNode);
|
||||
minNode = this._cascadingCut(parent, minNode);
|
||||
} else {
|
||||
node.isMarked = true;
|
||||
}
|
||||
}
|
||||
return minNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge all trees of the same order together until there are no two trees of
|
||||
* the same order.
|
||||
* @param minNode The current minimum node.
|
||||
* @return The new minimum node.
|
||||
*/
|
||||
private _consolidate(minNode: Node<K, V>): Node<K, V> | null {
|
||||
|
||||
const aux = [];
|
||||
tmpIt.reset(minNode);
|
||||
while (tmpIt.hasNext()) {
|
||||
let current = tmpIt.next();
|
||||
|
||||
// If there exists another node with the same degree, merge them
|
||||
let auxCurrent = aux[current.degree];
|
||||
while (auxCurrent) {
|
||||
if (this._compare(current, auxCurrent) > 0) {
|
||||
const temp = current;
|
||||
current = auxCurrent;
|
||||
auxCurrent = temp;
|
||||
}
|
||||
this._linkHeaps(auxCurrent, current);
|
||||
aux[current.degree] = null;
|
||||
current.degree++;
|
||||
auxCurrent = aux[current.degree];
|
||||
}
|
||||
|
||||
aux[current.degree] = current;
|
||||
}
|
||||
|
||||
let newMinNode = null;
|
||||
for (let i = 0; i < aux.length; i++) {
|
||||
const node = aux[i];
|
||||
if (node) {
|
||||
// Remove siblings before merging
|
||||
node.next = node;
|
||||
node.prev = node;
|
||||
newMinNode = this._mergeLists(newMinNode, node);
|
||||
}
|
||||
}
|
||||
return newMinNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a node from a node list.
|
||||
* @param node The node to remove.
|
||||
*/
|
||||
private _removeNodeFromList(node: Node<K, V>): void {
|
||||
const prev = node.prev;
|
||||
const next = node.next;
|
||||
prev.next = next;
|
||||
next.prev = prev;
|
||||
node.next = node;
|
||||
node.prev = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links two heaps of the same order together.
|
||||
*
|
||||
* @private
|
||||
* @param max The heap with the larger root.
|
||||
* @param min The heap with the smaller root.
|
||||
*/
|
||||
private _linkHeaps(max: Node<K, V>, min: Node<K, V>): void {
|
||||
this._removeNodeFromList(max);
|
||||
min.child = this._mergeLists(max, min.child);
|
||||
max.parent = min;
|
||||
max.isMarked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two lists of nodes together.
|
||||
*
|
||||
* @private
|
||||
* @param a The first list to merge.
|
||||
* @param b The second list to merge.
|
||||
* @return The new minimum node from the two lists.
|
||||
*/
|
||||
private _mergeLists(a: Node<K, V> | null, b: Node<K, V> | null): Node<K, V> | null {
|
||||
if (!a) {
|
||||
if (!b) {
|
||||
return null;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
if (!b) {
|
||||
return a;
|
||||
}
|
||||
|
||||
const temp = a.next;
|
||||
a.next = b.next;
|
||||
a.next.prev = a;
|
||||
b.next = temp;
|
||||
b.next.prev = b;
|
||||
|
||||
return this._compare(a, b) < 0 ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of a node list.
|
||||
* @param node A node within the node list.
|
||||
* @return The size of the node list.
|
||||
*/
|
||||
private _getNodeListSize(node: Node<K, V>): number {
|
||||
let count = 0;
|
||||
let current = node;
|
||||
|
||||
do {
|
||||
count++;
|
||||
if (current.child) {
|
||||
count += this._getNodeListSize(current.child);
|
||||
}
|
||||
current = current.next;
|
||||
} while (current !== node);
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user