Compare commits

...

70 Commits

Author SHA1 Message Date
Alexander Rose
3667092327 tweak cube clamp param 2022-11-08 22:28:47 -08:00
Alexander Rose
842824057b add param to clamp cube values 2022-11-08 22:17:23 -08:00
Alexander Rose
6a32f85e60 Merge pull request #603 from giagitom/transparent-bg-fixes 2022-11-06 16:42:27 -08:00
giagitom
f29c62ec33 Transparent bg fix 2022-10-30 12:06:32 +01:00
Alexander Rose
d77981230b mmcif schema update 2022-10-29 12:15:29 -07:00
dsehnal
e2b92c15f0 PluginContext.initContainer options 2022-10-27 08:41:01 +02:00
dsehnal
14614f4803 3.23.0 2022-10-19 13:11:47 +02:00
dsehnal
37d3489b07 changelog 2022-10-19 13:09:02 +02:00
David Sehnal
f81225cc0d Merge pull request #592 from molstar/volume/defaults-for-em
Change EM Volume Streaming default Auto
2022-10-19 13:08:17 +02:00
dsehnal
eb47f43940 Change EM Volume Streaming default Auto 2022-10-19 13:05:16 +02:00
dsehnal
7618a5e2c9 pr template 2022-10-19 12:41:24 +02:00
David Sehnal
ab3ff842b2 Merge pull request #590 from molstar/reusable-canvas
Built-in support for mouting/unmounting PluginContext
2022-10-19 09:55:31 +02:00
dsehnal
82f0f92c15 remove unused code 2022-10-18 15:20:48 +02:00
dsehnal
545d9434d8 Add PluginContext.initContainer/canvas3dInitialized and their usage 2022-10-18 09:16:08 +02:00
dsehnal
bbc43d5113 Add PluginContext.mount/unmount 2022-10-18 08:41:29 +02:00
dsehnal
a6709acf65 3.22.0 2022-10-17 20:44:13 +02:00
dsehnal
509a027742 changelog 2022-10-17 20:41:35 +02:00
David Sehnal
7244023233 Merge pull request #589 from molstar/picking-granuality-v2
Volume.PickingGranuality custom property
2022-10-17 20:39:47 +02:00
dsehnal
c5f987d8b2 getSliceLoci tweak 2022-10-17 20:36:39 +02:00
dsehnal
793696d4c0 Volume slice granuality 2022-10-17 20:34:11 +02:00
dsehnal
305ca05f04 tweaks 2022-10-17 20:24:48 +02:00
dsehnal
f4d7d1920a typos 2022-10-17 20:12:19 +02:00
dsehnal
458aad0161 Volume.PickingGranuality custom property 2022-10-17 20:05:56 +02:00
dsehnal
9e3132461f 3.21.0 2022-10-17 17:32:57 +02:00
dsehnal
8301291215 changelog 2022-10-17 17:29:52 +02:00
David Sehnal
daed14e228 Merge pull request #588 from midlik/picking-whole-isosurfaces
New volume isosurface param pickingGranularity: voxels|surfaces
2022-10-17 17:27:34 +02:00
David Sehnal
7db82c5ba5 Merge pull request #584 from arussell123/master
Prevent component controls collapsing when option is selected
2022-10-17 16:43:01 +02:00
Alexander Rose
91d03c22c2 3.20.0 2022-10-16 21:42:39 -07:00
Alexander Rose
bc188f0d2b changelog 2022-10-16 21:37:18 -07:00
Alexander Rose
3981225824 package updates 2022-10-16 21:34:59 -07:00
Alexander Rose
1886d9d72f add structure-index color theme 2022-10-16 21:28:06 -07:00
Alexander Rose
2a7dec8892 Merge pull request #583 from jpattle/model-index-carbon-color
Model index updates & carbon color
2022-10-16 19:43:23 -07:00
Alexander Rose
35d4a5b297 Merge branch 'master' into model-index-carbon-color 2022-10-16 19:39:22 -07:00
Alexander Rose
26345bfa50 tweak 2022-10-16 17:42:43 -07:00
Alexander Rose
8c9b8676dd handle 'not enough samples' in distinctColors 2022-10-16 16:43:50 -07:00
Alexander Rose
5593c7a75f add Model.MaxIndex and use in model-index theme 2022-10-16 16:37:09 -07:00
Alexander Rose
5b70c14ffe tweak theme descriptions 2022-10-16 16:35:51 -07:00
Alexander Rose
5e4d611044 tweak changelog 2022-10-16 16:33:52 -07:00
David Sehnal
7ab9d57156 Merge pull request #545 from giagitom/lookup3d
adding nearest and distance to point methods to lookup3d
2022-10-14 21:46:33 +02:00
Adam Midlik
9ea6f51126 New volume isosurface param pickingGranularity: voxels|surfaces 2022-10-13 00:57:39 +02:00
giagitom
649fe4f4f0 Lint fix 2022-10-12 16:57:56 +02:00
giagitom
53df86c585 Avoid using unnecessary extractMinimum from heap if k=1 on nearest search, add nearest method as unreleased. 2022-10-12 16:14:51 +02:00
Alice Russell
87c708e3c1 Remove action state from being set to undefined when action selected to stop options from minimising on selection 2022-10-12 10:58:30 +01:00
Jason Pattle
ba927b0490 returned clone of theme params for model and trajectory index themes, added contributor name 2022-10-12 08:30:52 +01:00
Jason Pattle
2a09725c98 added the new model-index color theme as an option in the illustrative color-theme 2022-10-12 08:30:10 +01:00
Jason Pattle
9fa0d17933 removed carbon color adjustment option 2022-10-12 08:29:45 +01:00
Jason Pattle
8d9f8a996a Updated change log with changes to model-index and element-symbol 2022-10-12 08:29:17 +01:00
giagitom
8814b60d0b Increased performances of lookup3d nearest search. 2022-10-11 18:12:27 +02:00
Jason Pattle
541c07c53a Added a parameter to make adjusting the carbon color by the same saturation and lightness carbon colors optional 2022-10-11 16:19:17 +01:00
Jason Pattle
6cbed80815 updated the default color palette and removed the redundant model color map 2022-10-11 16:07:44 +01:00
Jason Pattle
a3c1fdc0f4 Added the model index theme provider as an option for the carbon color when selecting the element-symbol color theme 2022-10-11 15:53:05 +01:00
Jason Pattle
ddf789b01c added a new model-index color theme based off the trajectory index theme but instead using the Model.Index structure property 2022-10-11 15:50:23 +01:00
Jason Pattle
ab86cc0bf3 Renamed the model-index color theme file to trajectory-index 2022-10-11 15:37:04 +01:00
Jason Pattle
dc8fab5820 [BREAKING CHANGE] renamed the model-index color theme and its usages to trajectory-index to better reflect the functionality of the color theme 2022-10-11 15:35:29 +01:00
giagitom
813c4f845a Merge branch 'master' into lookup3d 2022-10-09 15:10:37 +02:00
Alexander Rose
6ed42e9521 add mipmap-based blur for skybox backgrounds 2022-10-08 14:54:29 -07:00
Alexander Rose
fb01ba60ec use resources object to get textures for smaa pass 2022-10-08 14:08:07 -07:00
Alexander Rose
ea4210ded5 add willReadFrequently option to sdf text 2d context 2022-10-08 14:06:25 -07:00
Alexander Rose
75e5cf54d6 remove deprecated vscode extension from recommendations 2022-10-08 14:05:12 -07:00
Jason Pattle
7cebc85a95 fixed linting warnings 2022-10-06 13:02:15 +01:00
Jason Pattle
c00faafa6d Returned a clone of the element symbol params instead of the original const, removing a todo comment 2022-10-06 11:42:53 +01:00
Jason Pattle
c9b14f0742 Updated the element symbol color theme so that the carbon color is also adjusted by saturation and brightness props 2022-10-06 09:13:33 +01:00
giagitom
d7cbd5570c Implement lookup & grid nearest search using fibonacci heap 2022-09-30 15:44:10 +02:00
giagitom
378f4f8304 Merge branch 'master' into lookup3d 2022-09-19 10:22:43 +02:00
giagitom
0bdcfea276 Merge branch 'master' into lookup3d 2022-09-16 17:45:20 +02:00
giagitom
718f76313f Adding nearest method to lookup3d at unit and structure level. 2022-09-16 17:32:07 +02:00
giagitom
9f72465052 remove unnecessary 2022-09-02 10:21:56 +02:00
giagitom
ac33b4a322 Adding distanceToVec in sphere3d namespace 2022-09-02 10:20:51 +02:00
giagitom
911c844e54 Remove logs and unused variables 2022-09-01 18:09:04 +02:00
giagitom
12b9655565 adding nearest and distance to point methods to lookup3d 2022-09-01 17:41:09 +02:00
52 changed files with 2352 additions and 1269 deletions

10
.github/pull_request_template.md vendored Normal file
View 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`

View File

@@ -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"

View File

@@ -6,6 +6,33 @@ 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

2280
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.19.0",
"version": "3.23.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -97,38 +97,38 @@
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.2.1",
"@graphql-codegen/cli": "^2.13.1",
"@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.5",
"@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.1.1",
"@types/jest": "^29.1.2",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"@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.24.0",
"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.1.2",
"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.55.0",
"sass-loader": "^13.0.2",
"sass-loader": "^13.1.0",
"simple-git": "^3.14.1",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
@@ -142,20 +142,20 @@
"@types/benchmark": "^2.1.2",
"@types/compression": "1.7.2",
"@types/express": "^4.17.14",
"@types/node": "^16.11.62",
"@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.7",
"swagger-ui-dist": "^4.14.2",
"swagger-ui-dist": "^4.14.3",
"tslib": "^2.4.0",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"

View File

@@ -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: {} } }
}
}
});

View File

@@ -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);

View File

@@ -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 }) {

View File

@@ -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);

View File

@@ -72,6 +72,7 @@ export const Backgrounds = PluginBehavior.create<{ }>({
lightness: 0,
saturation: 0,
opacity: 1,
blur: 0.3,
}
}
}, 'Purple Nebula Skybox'],

View File

@@ -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),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -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';
@@ -192,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,

View File

@@ -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';

View File

@@ -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)

View File

@@ -57,7 +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.0, 1.0); // prevents black artifacts on specular highlight with transparent background
outgoingLight = clamp(outgoingLight, 0.01, 0.99); // prevents black artifacts on specular highlight with transparent background
gl_FragColor = vec4(outgoingLight, color.a);
#endif

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.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
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.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
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.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
*/

View File

@@ -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]);
});
});
});

View File

@@ -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>
}
}

View File

@@ -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;
}

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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 };
}
});
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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];

View File

@@ -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);
}
}
}

View File

@@ -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';
}
};
}

View File

@@ -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 };

View File

@@ -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);
});

View File

@@ -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;
}

View File

@@ -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 />

View File

@@ -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;
}

View File

@@ -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)();
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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>;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -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);
}
}
});

View File

@@ -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),

View File

@@ -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 }),

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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')

View File

@@ -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);

View File

@@ -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
};

View 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
};

View 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
};

View 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);
});
});

View File

@@ -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[] = [];

View 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;
}
}