Compare commits

...

62 Commits

Author SHA1 Message Date
Alexander Rose
0e843c20cc 5.4.2 2025-12-07 11:00:41 -08:00
Alexander Rose
ecaf19c5fb changelog 2025-12-07 10:59:26 -08:00
Alexander Rose
f024aeef2c schema updates 2025-12-07 10:58:03 -08:00
Alexander Rose
9d9985f117 package updates 2025-12-07 10:57:55 -08:00
Alexander Rose
a0f7349ef6 reduce automatic quality on standalone HMD devices 2025-12-06 10:50:00 -08:00
Alexander Rose
01407427d2 Merge pull request #1714 from giagitom/postprocessing-improvements
postprocessing improvements
2025-12-06 10:31:39 -08:00
Alexander Rose
3dee03d9b6 cleanup & changelog 2025-12-06 10:26:59 -08:00
Alexander Rose
737f6593be Merge pull request #1712 from molstar/import-tweaks
Import tweaks
2025-12-06 10:15:41 -08:00
giagitom
068e10dd40 fix whitespaces 2025-12-05 11:16:27 +01:00
giagitom
c1ba5248b0 postprocessing improvements 2025-12-04 16:06:30 +01:00
Alexander Rose
4af0f22ac0 remove dependency between mol-util and mol-io 2025-11-23 19:19:17 -08:00
Alexander Rose
25a67e1176 remove dependency between vec4 and sphere3d 2025-11-23 19:09:06 -08:00
Alexander Rose
a8fcd501d6 remove dependency between mol-util and mol-canvas3d 2025-11-23 19:05:48 -08:00
Alexander Rose
573ee92889 remove dependency between mol-util and mol-script 2025-11-23 19:04:58 -08:00
Alexander Rose
2558d6fada remove dependency between mol-math and mol-geo 2025-11-23 19:01:11 -08:00
Alexander Rose
2cf3f8d62b move DensityTextureData type out of shared module
- not reused, depends on mol-gl
2025-11-23 19:00:12 -08:00
Alexander Rose
589d89b0d5 remove import from mol-geo in mol-gl
- too many dependencies
- not usefull, mostly for documentation
2025-11-23 18:58:31 -08:00
Alexander Rose
7cc7b77460 add missing method in gl shim 2025-11-23 18:55:40 -08:00
Alexander Rose
e8a9995bef use more direct imports
- avoid importing from re-exports
- helps to create smaller files with some bundlers
2025-11-23 18:55:11 -08:00
Alexander Rose
74ff283e00 5.4.1 2025-11-16 10:18:09 -08:00
Alexander Rose
1ecb960b82 changelog 2025-11-16 10:17:14 -08:00
Alexander Rose
387d59f97b Merge branch 'master' of https://github.com/molstar/molstar 2025-11-16 10:13:12 -08:00
Alexander Rose
d993082f24 5.4.0 2025-11-16 10:12:28 -08:00
Alexander Rose
5eaa73d56d changelog 2025-11-16 10:11:07 -08:00
Alexander Rose
b9428fd3cd Merge pull request #1708 from molstar/volume-improvements
Volume improvements
2025-11-16 10:10:11 -08:00
Alexander Rose
97d180b79d use Number.isNaN 2025-11-16 10:07:45 -08:00
Alexander Rose
25bd915ea5 optimize wrapped volume 2025-11-16 10:04:10 -08:00
Alexander Rose
f8fdffdc44 Update src/mol-model-formats/volume/ccp4.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-11-16 09:45:14 -08:00
David Sehnal
d11aa6ea77 improve guessCifVariant (#1709) 2025-11-16 17:53:16 +01:00
Alexander Rose
fc3c7997ea package updates 2025-11-15 16:54:26 -08:00
Alexander Rose
b3aecf8de4 package updates 2025-11-15 16:46:15 -08:00
Alexander Rose
f3581e62ef schema updates 2025-11-15 16:41:09 -08:00
Alexander Rose
88e7fe508f package updates 2025-11-15 16:32:09 -08:00
Alexander Rose
98049ed02d Merge branch 'master' of https://github.com/molstar/molstar into volume-improvements 2025-11-15 16:25:44 -08:00
Alexander Rose
194092ed67 Merge pull request #1707 from rxht/text-input-chinese-support
Add Chinese language support to the text input box.
2025-11-15 16:23:21 -08:00
Alexander Rose
e96157c890 changelog 2025-11-15 16:19:40 -08:00
Alexander Rose
a028c1ef42 Merge branch 'master' of https://github.com/molstar/molstar into pr/rxht/1707 2025-11-15 16:16:32 -08:00
Alexander Rose
ad2b5e687d Volume improvements
- Add `Volume.periodicity`
- Wrap isosurfaces for periodic volumes
- Fix dimensions for slices
2025-11-15 16:09:47 -08:00
Alexander Rose
8ba19f0be4 Merge pull request #1701 from midlik/bounding-spheres
Bounding spheres include element radius
2025-11-15 09:29:55 -08:00
Alexander Rose
bccc68f6df Merge branch 'master' of https://github.com/molstar/molstar into pr/midlik/1701 2025-11-15 09:06:55 -08:00
Alexander Rose
026a05d03d formating 2025-11-15 09:06:46 -08:00
Alexander Rose
2b4741c8ee Use PluginCommands to set canvas3d props in camera behavior 2025-11-15 08:52:25 -08:00
Alexander Rose
7960ee06d4 Fix default trackball animated spin speed 2025-11-15 08:50:24 -08:00
Alexander Rose
f73f5af131 Fix direct-volume not drawn in illumination mode 2025-11-15 08:48:22 -08:00
rxht
3123110aa4 Add Chinese language support to the text input box. 2025-11-14 10:56:15 +08:00
midlik
154063638d MVS: Allow canvas background interpolation (#1704) 2025-11-13 12:02:10 +01:00
midlik
a720b98365 MVS transformed primitives (#1705)
* MVS: Fix primitives in root not being transformed with reference structure

* MultilayerColorTheme only preferSmoothing when a nested theme prefers

* MVSAnnotationColorTheme do not prefer smoothing

* Update file header
2025-11-11 16:47:35 +01:00
Adam Midlik
d4a2937e0b Merge branch 'master' into bounding-spheres 2025-11-11 15:39:20 +00:00
midlik
b0ca7ffbb7 Fix all-selector color not applying on substructure (#1700)
* Fix all-selector color not applying on substructure

* Fix CHANGELOG

* Size uniform computed from the first included location
2025-11-11 10:32:01 +01:00
David Sehnal
c42b738abe MVS: Fix appendSnapshots when loading MVSX (#1702) 2025-11-11 10:31:06 +01:00
Alexander Rose
ab0d0fec53 Merge pull request #1697 from midlik/label-boundary
Fix bounding sphere computation for 3D text
2025-11-09 17:49:50 -08:00
Alexander Rose
8d96131962 changelog 2025-11-09 17:49:27 -08:00
Alexander Rose
95bbcd8b24 Merge branch 'master' of https://github.com/molstar/molstar into pr/midlik/1697 2025-11-09 17:49:06 -08:00
Alexander Rose
a21f5c2c23 Add viewport button to toggle illumination mode 2025-11-09 16:45:26 -08:00
Adam Midlik
94b7b1281c Merge branch 'master' into bounding-spheres 2025-11-07 10:19:00 +00:00
Adam Midlik
16dba586df Relax camera limits to allow focusing any selection with >1 atom 2025-11-07 10:13:39 +00:00
midlik
72b761f959 Fix ugly camera clipping in snapshot transitions (#1699)
* Fix ugly camera clipping in snapshot transitions

* Update CHANGELOG.md

---------

Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2025-11-06 16:04:20 +01:00
Adam Midlik
943d81cbf9 Fix bounding sphere computation for 3D text 2025-11-06 09:28:09 +00:00
Adam Midlik
ea612c3acb Unit and loci bounding sphere includes VDW or coarse sphere radius 2025-10-31 12:50:51 +00:00
Adam Midlik
a1308645e5 Vec3.orthogonalize handle special cases (fixes trackpad lock in MVS) 2025-10-29 14:04:18 +00:00
Adam Midlik
794b705184 MVS: Fix CIF annotations with no selector columns being ignored 2025-10-28 11:56:40 +00:00
Adam Midlik
66264abe50 MVS: Fix tooltips persisting across snapshots 2025-10-28 11:25:29 +00:00
136 changed files with 3382 additions and 4170 deletions

View File

@@ -5,7 +5,33 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v5.3.0] - 2025-11-5
## [v5.4.2] - 2025-12-07
- Fix postprocessing issues with SSAO and outlines for large structures (#1387)
- Reduce automatic quality on standalone HMD devices
## [v5.4.1] - 2025-11-16
- Fix ugly camera clipping in snapshot transitions
- Add viewport button to toggle illumination mode
- Fix bounding sphere computation for 3D text
- Structure bounding sphere includes atom VDW radii / coarse sphere radii
- Relax camera limits to allow focusing any selection with >1 atom
- MolViewSpec
- Fix `appendSnapshots` when loading MVSX
- Fix all-selector color not applying on substructure
- Fix primitives in root not being transformed with reference structure
- Color themes do not prefer smoothing (improves performance in animations)
- Allow canvas background interpolation
- Fix `direct-volume` not drawn in illumination mode
- Fix default trackball animated spin speed
- Use `PluginCommands` to set canvas3d props in camera behavior
- Volume improvements
- Add `Volume.periodicity`
- Wrap isosurfaces for periodic volumes
- Fix dimensions for slices
- Add support for Input Method Editor (IME) to text params input
- Update `guessCifVariant` to detect density files not generated by the VolumeServer
## [v5.3.0] - 2025-11-05
- Update loading message in MVS Stories Viewer
- Add `Canvas3D.setAttribs`
- Fix `normalizeWheel` "spin" calculation fallback

6393
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "5.3.0",
"version": "5.4.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -131,51 +131,51 @@
"@types/gl": "^6.0.5",
"@types/jest": "^30.0.0",
"@types/pngjs": "^6.0.5",
"@types/react": "^18.3.24",
"@types/react": "^18.3.26",
"@types/react-dom": "^18.3.7",
"@types/webxr": "^0.5.23",
"@typescript-eslint/eslint-plugin": "^8.44.1",
"@typescript-eslint/parser": "^8.44.1",
"@types/webxr": "^0.5.24",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"benchmark": "^2.1.4",
"concurrently": "^9.2.1",
"cpx2": "^8.0.0",
"css-loader": "^7.1.2",
"esbuild": "^0.25.10",
"esbuild": "^0.27.1",
"esbuild-jest-transform": "^2.0.1",
"esbuild-sass-plugin": "^3.3.1",
"eslint": "^9.36.0",
"eslint": "^9.39.1",
"fs-extra": "^11.3.2",
"http-server": "^14.1.1",
"jest": "^30.2.0",
"jpeg-js": "^0.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.93.2",
"simple-git": "^3.28.0",
"sass": "^1.94.2",
"simple-git": "^3.30.0",
"tsc-alias": "^1.8.16",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
},
"dependencies": {
"@types/argparse": "^2.0.17",
"@types/benchmark": "^2.1.5",
"@types/compression": "1.8.1",
"@types/express": "^5.0.3",
"@types/node": "^20.19.17",
"@types/express": "^5.0.6",
"@types/node": "^20.19.25",
"@types/node-fetch": "^2.6.13",
"@types/swagger-ui-dist": "3.30.6",
"argparse": "^2.0.1",
"compression": "^1.8.1",
"cors": "^2.8.5",
"express": "^5.1.0",
"express": "^5.2.1",
"h264-mp4-encoder": "^1.0.12",
"immutable": "^5.1.3",
"immutable": "^5.1.4",
"io-ts": "^2.2.22",
"mutative": "^1.3.0",
"node-fetch": "^2.7.0",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"rxjs": "^7.8.2",
"swagger-ui-dist": "^5.29.0",
"swagger-ui-dist": "^5.30.3",
"tslib": "^2.8.1",
"util.promisify": "^1.1.3"
},

View File

@@ -531,7 +531,7 @@ export class Viewer {
} else if (format === 'mvsx') {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
const parsed = await loadMVSX(this.plugin, ctx, data);
const parsed = await loadMVSX(this.plugin, ctx, data, { doNotClearAssets: options?.appendSnapshots });
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
}));
} else {

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env node
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 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 * as fs from 'fs';
@@ -38,7 +39,7 @@ function print(volume: Volume) {
}
async function doMesh(volume: Volume, filename: string) {
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5), wrap: 'auto' })).run();
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
// Export the mesh in OBJ format.

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -33,7 +33,7 @@ import { MVSTreeSchema } from './tree/mvs/mvs-tree';
const DefaultFocusOptions = {
minRadius: 5,
minRadius: 1,
extraRadius: 0,
};
const DefaultCanvasBackgroundColor = ColorNames.white;

View File

@@ -117,7 +117,7 @@ export function MVSAnnotationColorTheme(ctx: ThemeDataContext, props: MVSAnnotat
return {
factory: MVSAnnotationColorTheme,
granularity: 'groupInstance',
preferSmoothing: true,
preferSmoothing: false,
color: color,
props: props,
description: 'Assigns colors based on custom MolViewSpec annotation data.',

View File

@@ -112,11 +112,18 @@ export const MVSXFormatProvider: DataFormatProvider<{}, StateObjectRef<Mvs>, any
* add all contained files to `plugin`'s asset manager,
* and parse the main file in the archive as MVSJ.
* Return parsed MVS data and `sourceUrl` for resolution of relative URIs. */
export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext, data: Uint8Array<ArrayBuffer>, mainFilePath: string = 'index.mvsj'): Promise<{ mvsData: MVSData, sourceUrl: string }> {
export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext, data: Uint8Array<ArrayBuffer>, mainFilePathOrOptions?: string | { mainFilePath?: string, doNotClearAssets?: boolean }): Promise<{ mvsData: MVSData, sourceUrl: string }> {
// TODO: on next major version, streamline mainFilePathOrOptions
if (typeof mainFilePathOrOptions === 'string') {
mainFilePathOrOptions = { mainFilePath: mainFilePathOrOptions };
}
const mainFilePath = mainFilePathOrOptions?.mainFilePath ?? 'index.mvsj';
const doNotClearAssets = mainFilePathOrOptions?.doNotClearAssets ?? false;
// Ensure at most one generation of MVSX file assets exists in the asset manager.
// Hopefully, this is a reasonable compromise to ensure MVSX files work in multi-snapshot
// states.
clearMVSXFileAssets(plugin);
if (!doNotClearAssets) clearMVSXFileAssets(plugin);
const archiveId = `ni,MurmurHash3_128;${murmurHash3_128_fromBytes(data, 42)}`;
let files: { [path: string]: Uint8Array<ArrayBuffer> };
@@ -160,7 +167,7 @@ export async function loadMVSData(plugin: PluginContext, data: MVSData | StringL
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
}
await plugin.runTask(Task.create('Load MVSX file', async ctx => {
const parsed = await loadMVSX(plugin, ctx, data as Uint8Array<ArrayBuffer>);
const parsed = await loadMVSX(plugin, ctx, data as Uint8Array<ArrayBuffer>, { doNotClearAssets: options?.appendSnapshots });
await loadMVS(plugin, parsed.mvsData, { sanityChecks: true, ...options, sourceUrl: parsed.sourceUrl });
}));
} else {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
*/
@@ -72,7 +72,7 @@ export const DefaultMultilayerColorThemeProps: MultilayerColorThemeProps = { lay
* If a nested theme provider has `ensureCustomProperties` methods, these will not be called automatically
* (the caller must ensure that any required custom properties be attached). */
function makeMultilayerColorTheme(ctx: ThemeDataContext, props: MultilayerColorThemeProps, colorThemeRegistry: ColorTheme.Registry): ColorTheme<MultilayerColorThemeParams> {
const { colorLayers, granularity } = makeLayers(ctx, props, colorThemeRegistry);
const { colorLayers, granularity, preferSmoothing } = makeLayers(ctx, props, colorThemeRegistry);
function structureElementColor(loc: StructureElement.Location, isSecondary: boolean): Color {
for (const layer of colorLayers) {
@@ -101,7 +101,7 @@ function makeMultilayerColorTheme(ctx: ThemeDataContext, props: MultilayerColorT
return {
factory: (ctx_, props_) => makeMultilayerColorTheme(ctx_, props_, colorThemeRegistry),
granularity,
preferSmoothing: true,
preferSmoothing,
color: color,
props: props,
description: 'Combines colors from multiple color themes.',
@@ -136,6 +136,7 @@ interface ColorLayer {
function makeLayers(ctx: ThemeDataContext, props: MultilayerColorThemeProps, colorThemeRegistry: ColorTheme.Registry) {
const colorLayers: ColorLayer[] = [];
let granularityFlags = 0;
let preferSmoothing = false;
for (let i = props.layers.length - 1; i >= 0; i--) { // iterate from the end to get top layer first, bottom layer last
const layer = props.layers[i];
const themeProvider = colorThemeRegistry.get(layer.theme.name);
@@ -175,8 +176,9 @@ function makeLayers(ctx: ThemeDataContext, props: MultilayerColorThemeProps, col
default:
console.warn(`Skipping color theme '${layer.theme.name}', cannot process granularity '${theme.granularity}'`);
}
if (theme.preferSmoothing) preferSmoothing = true;
}
return { colorLayers, granularity: granularityNameFromFlags(granularityFlags) };
return { colorLayers, granularity: granularityNameFromFlags(granularityFlags), preferSmoothing };
}

View File

@@ -464,12 +464,15 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
const refs = getPrimitiveStructureRefs(tree);
const clip = clippingForNode(tree);
const data = UpdateTarget.apply(updateParent, MVSInlinePrimitiveData, { node: tree as any });
UpdateTarget.setMvsDependencies(data, refs); // MVSInlinePrimitiveData must depend on `refs` because it caches positions
return applyPrimitiveVisuals(data, refs, clip);
},
primitives_from_uri(updateParent: UpdateTarget, tree: MolstarNode<'primitives_from_uri'>, context: MolstarLoadingContext): UpdateTarget {
const data = UpdateTarget.apply(updateParent, MVSDownloadPrimitiveData, { uri: tree.params.uri, format: tree.params.format });
const refs = new Set(tree.params.references);
const clip = clippingForNode(tree);
return applyPrimitiveVisuals(data, new Set(tree.params.references), clip);
const data = UpdateTarget.apply(updateParent, MVSDownloadPrimitiveData, { uri: tree.params.uri, format: tree.params.format });
UpdateTarget.setMvsDependencies(data, refs); // MVSInlinePrimitiveData must depend on `refs` because it caches positions
return applyPrimitiveVisuals(data, refs, clip);
},
};

View File

@@ -5,13 +5,16 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
import { Scene } from '../mol-gl/scene';
import { assertUnreachable } from '../mol-util/type-helpers';
import { Ray3D } from '../mol-math/geometry/primitives/ray3d';
import { Mat4 } from '../mol-math/linear-algebra/3d/mat4';
import { Vec4 } from '../mol-math/linear-algebra/3d/vec4';
import { Vec3 } from '../mol-math/linear-algebra/3d/vec3';
import { EPSILON } from '../mol-math/linear-algebra/3d/common';
export type { ICamera };

View File

@@ -5,8 +5,9 @@
*/
import { Camera } from '../camera';
import { Quat, Vec3 } from '../../mol-math/linear-algebra';
import { lerp } from '../../mol-math/interpolate';
import { Quat } from '../../mol-math/linear-algebra/3d/quat';
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
export { CameraTransitionManager };

View File

@@ -4,7 +4,9 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4';
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { Vec4 } from '../../mol-math/linear-algebra/3d/vec4';
export { Viewport };

View File

@@ -75,7 +75,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: 100, 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.' }),
minNear: PD.Numeric(1, { min: 0.1, max: 100, step: 0.1 }, { description: 'Minimal allowed distance of near clipping plane from the camera. 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({}),

View File

@@ -75,7 +75,7 @@ export const TrackballControlsParams = {
animate: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
spin: PD.Group({
speed: PD.Numeric(0.3, { min: -5, max: 5, step: 0.1 }, { description: 'Number of rotations per second' }),
speed: PD.Numeric(0.1, { min: -2, max: 2, step: 0.01 }, { description: 'Number of rotations per second' }),
}, { description: 'Spin the 3D scene around the x-axis in view space' }),
rock: PD.Group({
speed: PD.Numeric(0.3, { min: -5, max: 5, step: 0.1 }, { description: 'Number of oscilations per second' }),

View File

@@ -163,7 +163,7 @@ export class IlluminationPass {
const { gl, state } = this.webgl;
const markingEnabled = MarkingPass.isEnabled(props.marking);
const hasTransparent = scene.opacityAverage < 1;
const hasTransparent = scene.opacityAverage < 1 || scene.volumes.renderables.length > 0;
const hasMarking = markingEnabled && scene.markerAverage > 0;
this.transparentTarget.bind();
@@ -347,7 +347,7 @@ export class IlluminationPass {
const dofEnabled = DofPass.isEnabled(props.postprocessing);
const markingEnabled = MarkingPass.isEnabled(props.marking);
const hasTransparent = scene.opacityAverage < 1;
const hasTransparent = scene.opacityAverage < 1 || scene.volumes.renderables.length > 0;
const hasMarking = markingEnabled && scene.markerAverage > 0;
let needsUpdateCompose = false;

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { EquivalenceClasses } from '../util';
import { EquivalenceClasses } from '../util/equivalence-classes';
describe('equiv-classes', () => {
it('integer mod classes', () => {

View File

@@ -6,8 +6,8 @@
*/
import * as ColumnHelpers from './column-helpers';
import { Tensor as Tensors } from '../../mol-math/linear-algebra';
import { Tokens } from '../../mol-io/reader/common/text/tokenizer';
import { Tensor as Tensors } from '../../mol-math/linear-algebra/tensor';
import type { Tokens } from '../../mol-io/reader/common/text/tokenizer';
import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../mol-io/reader/common/text/number-parser';
interface Column<T> {

View File

@@ -6,7 +6,7 @@
import { Column } from './column';
import { sortArray } from '../util/sort';
import { StringBuilder } from '../../mol-util';
import { StringBuilder } from '../../mol-util/string-builder';
/** A collection of columns */
type Table<Schema extends Table.Schema = any> = {

View File

@@ -5,7 +5,9 @@
* @author Adam Midlik <midlik@gmail.com>
*/
import { sortArray, hash3, hash4, createRangeArray } from '../../util';
import { createRangeArray } from '../../util/array';
import { hash3, hash4 } from '../../util/hash-functions';
import { sortArray } from '../../util/sort';
import { Interval } from '../interval';
type Nums = ArrayLike<number>

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { iterableToArray } from '../util';
import { iterableToArray } from '../util/array';
// TODO: rename to "linear map" and just do key value mapping from index?

View File

@@ -4,8 +4,11 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Segmentation, OrderedSet, SortedArray, Interval } from '../int';
import { Iterator as _Iterator } from '../iterator';
import { Interval } from './interval';
import { OrderedSet } from './ordered-set';
import { Segmentation } from './segmentation';
import { SortedArray } from './sorted-array';
/** Pairs of min and max indices of sorted, non-overlapping ranges */
type SortedRanges<T extends number = number> = SortedArray<T>

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { hash2 } from '../util';
import { hash2 } from '../util/hash-functions';
/**
* Represents a pair of two integers as a double,

View File

@@ -4,7 +4,9 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Interval, OrderedSet, SortedArray } from '../../int';
import { Interval } from '../../int/interval';
import { OrderedSet } from '../../int/ordered-set';
import { SortedArray } from '../../int/sorted-array';
import { IntervalIterator } from '../interval-iterator';
describe('interval', () => {

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Column } from '../db';
import { Column } from '../db/column';
export interface Grouping<V, K> {
map: Map<K, V[]>,

View File

@@ -4,8 +4,10 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Interval } from '../int/interval';
import { OrderedSet } from '../int/ordered-set';
import { Segmentation } from '../int/segmentation';
import { Iterator } from '../iterator';
import { OrderedSet, Interval, Segmentation } from '../int';
/** Emits a segment of length one for each element in the interval that is also in the set */
export class IntervalIterator<I extends number = number> implements Iterator<Segmentation.Segment<I>> {

View File

@@ -122,7 +122,9 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
/** Creates color uniform */
function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
return createValueColor(color(NullLocation, false), colorData);
locationIt.reset();
const loc = locationIt.hasNext ? locationIt.move() : { location: NullLocation, isSecondary: false };
return createValueColor(color(loc.location, loc.isSecondary), colorData);
}
//

View File

@@ -5,7 +5,7 @@
*/
import { hashFnv32a } from '../../../mol-data/util';
import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
import { RenderableState } from '../../../mol-gl/renderable';
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';

View File

@@ -5,7 +5,7 @@
*/
import { hashFnv32a } from '../../../mol-data/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { LocationIterator } from '../../util/location-iterator';
import { RenderableState } from '../../../mol-gl/renderable';
import { calculateTransformBoundingSphere, createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
import { Sphere3D } from '../../../mol-math/geometry';

View File

@@ -8,7 +8,7 @@
import { ChunkedArray } from '../../../mol-data/util';
import { Lines } from './lines';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { Cage } from '../../../mol-geo/primitive/cage';
import { Cage } from '../../primitive/cage';
export interface LinesBuilder {
add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number): void

View File

@@ -8,7 +8,7 @@ import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra';
import { ChunkedArray } from '../../../mol-data/util';
import { Mesh } from './mesh';
import { Primitive } from '../../primitive/primitive';
import { Cage } from '../../../mol-geo/primitive/cage';
import { Cage } from '../../primitive/cage';
import { addSphere } from './builder/sphere';
import { addCylinder } from './builder/cylinder';

View File

@@ -77,7 +77,9 @@ export function createValueSize(value: number, sizeData?: SizeData): SizeData {
/** Creates size uniform */
export function createUniformSize(locationIt: LocationIterator, sizeFn: LocationSize, sizeData?: SizeData): SizeData {
return createValueSize(sizeFn(NullLocation), sizeData);
locationIt.reset();
const location = locationIt.hasNext ? locationIt.move().location : NullLocation;
return createValueSize(sizeFn(location), sizeData);
}
export function createTextureSize(sizes: TextureImage<Uint8Array>, type: SizeType, sizeData?: SizeData): SizeData {

View File

@@ -8,7 +8,7 @@ import { ValueCell } from '../../../mol-util';
import { GeometryUtils } from '../geometry';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { TransformData } from '../transform-data';
import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
import { Theme } from '../../../mol-theme/theme';
import { SpheresValues } from '../../../mol-gl/renderable/spheres';
import { createColors } from '../color-data';

View File

@@ -2,12 +2,13 @@
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { ValueCell } from '../../../mol-util';
import { GeometryUtils } from '../geometry';
import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
import { TransformData } from '../transform-data';
import { Theme } from '../../../mol-theme/theme';
import { createColors } from '../color-data';
@@ -333,5 +334,5 @@ function getPadding(mappings: Float32Array, depths: Float32Array, charCount: num
const d = Math.abs(depths[i]);
if (d > maxDepth) maxDepth = d;
}
return scale * Math.max(maxDepth, maxOffset);
return Math.max(maxDepth, scale * maxOffset);
}

View File

@@ -8,7 +8,7 @@
import { ValueCell } from '../../../mol-util';
import { Sphere3D } from '../../../mol-math/geometry';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
import { TransformData } from '../transform-data';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';

View File

@@ -786,5 +786,6 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
stencilOp: function () { },
stencilOpSeparate: function () { },
unpackColorSpace: 'srgb',
makeXRCompatible: async function () { },
};
}

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { LinkedList } from '../mol-data/generic';
import { LinkedList } from '../mol-data/generic/linked-list';
import { GraphicsRenderObject } from './render-object';
type N = LinkedList.Node<GraphicsRenderObject>

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -38,6 +38,7 @@ const IsosurfaceSchema = {
uGridDim: UniformSpec('v3'),
uGridTexDim: UniformSpec('v3'),
uGridDataDim: UniformSpec('v3'),
uGridTransform: UniformSpec('m4'),
uGridTransformAdjoint: UniformSpec('m3'),
uScale: UniformSpec('v2'),
@@ -54,7 +55,7 @@ function valueChannel(ctx: WebGLContext, volumeData: Texture) {
return isWebGL2(ctx.gl) && volumeData.format === ctx.gl.RED ? 'red' : 'alpha';
}
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
@@ -71,6 +72,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uGridDataDim, gridDataDim);
ValueCell.update(v.uGridTransform, transform);
ValueCell.update(v.uGridTransformAdjoint, Mat3.adjointFromMat4(Mat3(), transform));
ValueCell.update(v.uScale, scale);
@@ -81,12 +83,12 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
ctx.namedComputeRenderables[IsosurfaceName].update();
} else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, gridDataDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) {
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) {
// console.log('uSize', Math.pow(2, levels))
const values: IsosurfaceValues = {
...QuadValues,
@@ -105,6 +107,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uGridDataDim: ValueCell.create(gridDataDim),
uGridTransform: ValueCell.create(transform),
uGridTransformAdjoint: ValueCell.create(Mat3.adjointFromMat4(Mat3(), transform)),
uScale: ValueCell.create(scale),
@@ -132,7 +135,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.clearColor(0, 0, 0, 0);
}
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
@@ -189,7 +192,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
groupTexture.attachFramebuffer(framebuffer, 1);
normalTexture.attachFramebuffer(framebuffer, 2);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, gridDataDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
ctx.state.currentRenderItemId = -1;
framebuffer.bind();
@@ -225,11 +228,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
*
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
*/
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
if (isTimingMode) ctx.timer.mark('extractIsosurface');
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, gridDataDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
if (isTimingMode) ctx.timer.markEnd('extractIsosurface');
return gv;

View File

@@ -4,7 +4,8 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4 } from '../mol-math/linear-algebra';
import { Mat4 } from '../mol-math/linear-algebra/3d/mat4';
import { Vec3 } from '../mol-math/linear-algebra/3d/vec3';
export interface Object3D {
readonly view: Mat4

View File

@@ -7,12 +7,12 @@
import { Program } from './webgl/program';
import { RenderableValues, Values, RenderableSchema, BaseValues } from './renderable/schema';
import { GraphicsRenderItem, ComputeRenderItem, GraphicsRenderVariant, MultiDrawBaseData, Transparency } from './webgl/render-item';
import { ValueCell } from '../mol-util';
import { ValueCell } from '../mol-util/value-cell';
import { idFactory } from '../mol-util/id-factory';
import { clamp } from '../mol-math/interpolate';
import { Frustum3D } from '../mol-math/geometry/primitives/frustum3d';
import { Plane3D } from '../mol-math/geometry/primitives/plane3d';
import { Sphere3D } from '../mol-math/geometry';
import { Sphere3D } from '../mol-math/geometry/primitives/sphere3d';
import { Vec4 } from '../mol-math/linear-algebra/3d/vec4';
import { WebGLStats } from './webgl/context';
import { isTimingMode } from '../mol-util/debug';

View File

@@ -9,8 +9,7 @@ import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem, Transparency } from '../webgl/render-item';
import { AttributeSpec, Values, GlobalUniformSchema, InternalSchema, TextureSpec, ElementsSpec, DefineSpec, InternalValues, BaseSchema, UniformSpec, GlobalTextureSchema, GlobalDefineValues, GlobalDefines, GlobalDefineSchema } from './schema';
import { ImageShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
import { InterpolationTypeNames } from '../../mol-geo/geometry/image/image';
import { ValueCell } from '../../mol-util/value-cell';
export const ImageSchema = {
...BaseSchema,
@@ -33,7 +32,8 @@ export const ImageSchema = {
uIsoLevel: UniformSpec('f'),
dInterpolation: DefineSpec('string', InterpolationTypeNames),
/** Same as `InterpolationTypeNames` in '../../mol-geo/geometry/image/image' */
dInterpolation: DefineSpec('string', ['nearest', 'catmulrom', 'mitchell', 'bspline']),
};
export type ImageSchema = typeof ImageSchema
export type ImageValues = Values<ImageSchema>

View File

@@ -22,6 +22,7 @@ uniform bool uInvert;
uniform vec3 uGridDim;
uniform vec3 uGridTexDim;
uniform vec3 uGridDataDim;
uniform mat4 uGridTransform;
uniform mat3 uGridTransformAdjoint;
@@ -93,20 +94,19 @@ vec4 baseVoxel(vec2 pos) {
}
vec4 getGroup(const in vec3 p) {
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
// note that we swap x and z because the texture is flipped around y
#if defined(dAxisOrder_012)
float group = p.z + p.y * gridDim.z + p.x * gridDim.z * gridDim.y; // 210
float group = p.z + p.y * uGridDataDim.z + p.x * uGridDataDim.z * uGridDataDim.y; // 210
#elif defined(dAxisOrder_021)
float group = p.y + p.z * gridDim.y + p.x * gridDim.y * gridDim.z; // 120
float group = p.y + p.z * uGridDataDim.y + p.x * uGridDataDim.y * uGridDataDim.z; // 120
#elif defined(dAxisOrder_102)
float group = p.z + p.x * gridDim.z + p.y * gridDim.z * gridDim.x; // 201
float group = p.z + p.x * uGridDataDim.z + p.y * uGridDataDim.z * uGridDataDim.x; // 201
#elif defined(dAxisOrder_120)
float group = p.x + p.z * gridDim.x + p.y * gridDim.x * gridDim.z; // 021
float group = p.x + p.z * uGridDataDim.x + p.y * uGridDataDim.x * uGridDataDim.z; // 021
#elif defined(dAxisOrder_201)
float group = p.y + p.x * gridDim.y + p.z * gridDim.y * gridDim.x; // 102
float group = p.y + p.x * uGridDataDim.y + p.z * uGridDataDim.y * uGridDataDim.x; // 102
#elif defined(dAxisOrder_210)
float group = p.x + p.y * gridDim.x + p.z * gridDim.x * gridDim.y; // 012
float group = p.x + p.y * uGridDataDim.x + p.z * uGridDataDim.x * uGridDataDim.y; // 012
#endif
return vec4(group > 16777215.5 ? vec3(1.0) : packIntToRGB(group), 1.0);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -48,7 +48,7 @@ vec2 getDepthTransparentWithAlpha(const in vec2 coords) {
}
bool isBackground(const in float depth) {
return depth > 0.9999;
return depth == 1.0;
}
float getPixelSize(const in vec2 coords, const in float depth) {
@@ -108,8 +108,8 @@ void main(void) {
transparentOutlineFlag = 0.0;
bestTransparentAlpha = 0.0;
}
vec2 depthPacked; // Pack depth in G/B channels
vec2 depthPacked; // Pack depth in G/B channels
float outlineTypeFlag = 0.0;
if (opaqueOutlineFlag > 0.0 && transparentOutlineFlag > 0.0) {
outlineTypeFlag = 0.75; // Both
@@ -121,7 +121,7 @@ void main(void) {
outlineTypeFlag = 0.25; // Opaque only
depthPacked = packUnitIntervalToRG(bestOpaqueDepth);
}
float alpha = clamp(bestTransparentAlpha, 0.0, 0.5) * 2.0; // limiting to range [0.0, 0.5] to improve alpha precision since we don't need a wider range
float packedFlagWithAlpha = pack2x4(vec2(outlineTypeFlag, alpha)); // pack outlineType with alpha
gl_FragColor = vec4(packedFlagWithAlpha, depthPacked.x, depthPacked.y, bestTransparentDepth);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -54,13 +54,13 @@ float getDepthTransparent(const in vec2 coords) {
}
bool isBackground(const in float depth) {
return depth > 0.9999; // handle depth packing precision issues
return depth == 1.0;
}
int squaredOutlineScale = dOutlineScale * dOutlineScale;
void getOutline(const in vec2 coords, out bool hasOpaque, out bool hasTransparent, out float opaqueDepth, out float transparentDepth, out float alpha) {
vec2 invTexSize = 1.0 / uTexSize;
hasOpaque = false;
hasTransparent = false;
opaqueDepth = 1.0;
@@ -81,14 +81,14 @@ void getOutline(const in vec2 coords, out bool hasOpaque, out bool hasTransparen
float sampleFlag = sampleFlagWithAlpha.x;
float sampleAlpha = clamp(sampleFlagWithAlpha.y * 0.5, 0.01, 1.0);
if ((sampleFlag > 0.20 && sampleFlag < 0.30) || (sampleFlag > 0.70 && sampleFlag < 0.80)) { // transparent || both
if (sampleOpaqueDepth < opaqueDepth) {
hasOpaque = true;
opaqueDepth = sampleOpaqueDepth;
}
}
if ((((sampleFlag > 0.45 && sampleFlag < 0.55) || (sampleFlag > 0.70 && sampleFlag < 0.80))) && sampleTransparentDepth < transparentDepth) { // transparent || both
hasTransparent = true;
transparentDepth = sampleTransparentDepth;
@@ -184,15 +184,15 @@ void main(void) {
if (hasOpaque) {
float viewDist = abs(getViewZ(outlineOpaqueDepth));
float fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
if (!uTransparentBackground) {
if (!uTransparentBackground) {
color.rgb = mix(uOutlineColor, uFogColor, fogFactor);
} else {
color.a = 1.0 - fogFactor;
color.rgb = mix(uOutlineColor, vec3(0.0), fogFactor);
}
}
}
#ifdef dBlendTransparency
#ifdef dBlendTransparency
if (hasTransparent) {
if (hasOpaque && outlineOpaqueDepth < outlineTransparentDepth) {
blendTransparency = false;

View File

@@ -1,9 +1,10 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ludovic Autin <ludovic.autin@gmail.com>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
export const ssaoBlur_frag = `
@@ -36,8 +37,7 @@ float getViewZ(const in float depth) {
}
bool isBackground(const in float depth) {
// checking for 1.0 is not enough, because of precision issues
return depth >= 0.999;
return depth == 1.0;
}
bool isNearClip(const in float depth) {
@@ -78,8 +78,9 @@ void main(void) {
float sum = 0.0;
float kernelSum = 0.0;
int halfKernelSize = dOcclusionKernelSize / 2;
// only if kernelSize is odd
for (int i = -dOcclusionKernelSize / 2; i <= dOcclusionKernelSize / 2; i++) {
for (int i = -halfKernelSize; i <= halfKernelSize; i++) {
if (abs(float(i)) > 1.0 && abs(float(i)) * pixelSize > 0.8) continue;
vec2 sampleCoords = coords + float(i) * offset;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -64,7 +64,7 @@ vec2 getNoiseVec2(const in vec2 coords) {
}
bool isBackground(const in float depth) {
return depth > 0.999; // handle precision issues with packed depth
return depth == 1.0;
}
float getDepth(const in vec2 coords, const in int transparentFlag) {

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.406, IHM 1.28, MA 1.4.7.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.408, IHM 1.28, MA 1.4.8.
*
* @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.406, IHM 1.28, MA 1.4.7.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.408, IHM 1.28, MA 1.4.8.
*
* @author molstar/ciftools package
*/

View File

@@ -40,7 +40,7 @@ export const CifCore_Schema = {
* in exceptional circumstances (Brock, 2025) may be reported as
* non-integral.
*
* Reference: Brock, C. P. (2025). Acta Cryst. A81, nnn-nnn.
* Reference: Brock, C. P. (2025). Acta Cryst. A81, 405-408.
*/
formula_units_z: float,
/**
@@ -336,7 +336,12 @@ export const CifCore_Schema = {
*/
site_symmetry_2: str,
/**
* Bond valence calculated from the bond distance.
* Valence assigned to the bond between the sites identified by
* _geom_bond.id calculated according to the bond-valence model
* (Brown, 2002) from the bond distance.
*
* Ref: Brown, I. D. (2002). The Chemical Bond in Inorganic Chemistry:
* the Bond-Valence Model, eq. (3.1). Oxford: Oxford University Press.
*/
valence: float,
},

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.406, IHM 1.28, MA 1.4.7.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.408, IHM 1.28, MA 1.4.8.
*
* @author molstar/ciftools package
*/
@@ -5078,7 +5078,7 @@ export const mmCIF_Schema = {
/**
* The type of data held in the dataset.
*/
content_type: Aliased<'target' | 'template structure' | 'polymeric template library' | 'spatial restraints' | 'target-template alignment' | 'coevolution MSA' | 'model coordinates' | 'input structure' | 'reference database' | 'other'>(str),
content_type: Aliased<'target' | 'template structure' | 'polymeric template library' | 'spatial restraints' | 'target-template alignment' | 'coevolution MSA' | 'model coordinates' | 'input structure' | 'reference database' | 'intermediate backbone' | 'intermediate sequence' | 'model quality assessment scores' | 'energy estimate' | 'experimental validation' | 'other'>(str),
/**
* Details for other content types.
*/
@@ -5131,7 +5131,7 @@ export const mmCIF_Schema = {
/**
* The mode of calculation of the QA metric.
*/
mode: Aliased<'local' | 'global' | 'local-pairwise' | 'per-feature' | 'per-feature-pair'>(str),
mode: Aliased<'local' | 'global' | 'local-pairwise' | 'per-feature' | 'per-feature-pair' | 'dihedral'>(str),
/**
* Identifier to the set of software used to calculate the QA metric.
* This data item is a pointer to the _ma_software_group.group_id in the

View File

@@ -6,7 +6,8 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { chunkedSubtask, RuntimeContext } from '../../../../mol-task';
import { RuntimeContext } from '../../../../mol-task/execution/runtime-context';
import { chunkedSubtask } from '../../../../mol-task/util/chunked';
import { StringLike } from '../../../common/string-like';
export { Tokenizer };

View File

@@ -6,8 +6,8 @@
*/
import { GridLookup3D } from '../../geometry';
import { sortArray } from '../../../mol-data/util';
import { OrderedSet } from '../../../mol-data/int';
import { sortArray } from '../../../mol-data/util/sort';
import { OrderedSet } from '../../../mol-data/int/ordered-set';
import { getBoundary } from '../boundary';
const xs = [0, 0, 1];

View File

@@ -7,7 +7,7 @@
import { PositionData } from './common';
import { Vec3 } from '../linear-algebra';
import { OrderedSet } from '../../mol-data/int';
import { OrderedSet } from '../../mol-data/int/ordered-set';
import { BoundaryHelper } from './boundary-helper';
import { Box3D } from '../geometry/primitives/box3d';
import { Sphere3D } from '../geometry/primitives/sphere3d';

View File

@@ -5,7 +5,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { Vec3 } from '../linear-algebra/3d/vec3';
import { Sphere3D } from './primitives/sphere3d';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)

View File

@@ -1,14 +1,15 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 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 { OrderedSet } from '../../mol-data/int';
import { Mat4, Tensor, Vec3, Vec2 } from '../linear-algebra';
import { OrderedSet } from '../../mol-data/int/ordered-set';
import { Mat4 } from '../linear-algebra/3d/mat4';
import { Vec3 } from '../linear-algebra/3d/vec3';
import { Tensor } from '../linear-algebra/tensor';
import { Box3D } from './primitives/box3d';
import { Texture } from '../../mol-gl/webgl/texture';
export interface PositionData {
x: ArrayLike<number>,
@@ -30,15 +31,6 @@ export type DensityData = {
maxRadius: number,
}
export type DensityTextureData = {
transform: Mat4,
texture: Texture,
bbox: Box3D,
gridDim: Vec3,
gridTexDim: Vec3
gridTexScale: Vec2
}
export interface RegularGrid3d {
box: Box3D,
dimensions: Vec3

View File

@@ -4,13 +4,16 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Box3D, DensityData, DensityTextureData } from '../geometry';
import { Box3D, DensityData } from '../geometry';
import { PositionData } from './common';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Texture } from '../../mol-gl/webgl/texture';
import { GaussianDensityTexture2d, GaussianDensityTexture3d } from './gaussian-density/gpu';
import { Task } from '../../mol-task/task';
import { GaussianDensityCPU } from './gaussian-density/cpu';
import { Mat4 } from '../linear-algebra/3d/mat4';
import { Vec3 } from '../linear-algebra/3d/vec3';
import { Vec2 } from '../linear-algebra/3d/vec2';
export const DefaultGaussianDensityProps = {
resolution: 1,
@@ -27,7 +30,14 @@ export type GaussianDensityTextureData = {
radiusFactor: number
resolution: number
maxRadius: number
} & DensityTextureData
transform: Mat4,
texture: Texture,
bbox: Box3D,
gridDim: Vec3,
gridTexDim: Vec3
gridDataDim: Vec3
gridTexScale: Vec2
}
export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) {
return Task.create('Gaussian Density', async ctx => {

View File

@@ -4,13 +4,15 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Box3D, fillGridDim } from '../../geometry';
import { Vec3, Mat4, Tensor } from '../../linear-algebra';
import { RuntimeContext } from '../../../mol-task';
import { PositionData } from '../common';
import { OrderedSet } from '../../../mol-data/int';
import { fillGridDim, PositionData } from '../common';
import { OrderedSet } from '../../../mol-data/int/ordered-set';
import { GaussianDensityProps, GaussianDensityData } from '../gaussian-density';
import { fasterExp } from '../../approx';
import { Box3D } from '../primitives/box3d';
import { Vec3 } from '../../linear-algebra/3d/vec3';
import { Tensor } from '../../linear-algebra/tensor';
import { Mat4 } from '../../linear-algebra/3d/mat4';
export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<GaussianDensityData> {
const { resolution, radiusOffset, smoothness } = props;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -99,8 +99,8 @@ export function GaussianDensityTexture3d(webgl: WebGLContext, position: Position
return finalizeGaussianDensityTexture(data);
}
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius }: _GaussianDensityTextureData): GaussianDensityTextureData {
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridDataDim, gridTexScale, radiusFactor, resolution, maxRadius }: _GaussianDensityTextureData): GaussianDensityTextureData {
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridDataDim, gridTexScale, radiusFactor, resolution, maxRadius };
}
function getTransform(scale: Vec3, bbox: Box3D) {
@@ -118,6 +118,7 @@ type _GaussianDensityTextureData = {
bbox: Box3D,
gridDim: Vec3,
gridTexDim: Vec3
gridDataDim: Vec3
gridTexScale: Vec2
radiusFactor: number
resolution: number
@@ -206,7 +207,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
// printTextureImage(readTexture(webgl, minDistTex), { scale: 0.75 });
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridDataDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
}
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
@@ -262,7 +263,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
setupGroupIdRendering(webgl, renderable);
render(texture, false);
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridDataDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
}
//

View File

@@ -7,15 +7,16 @@
* ported from NGL (https://github.com/arose/ngl), licensed under MIT
*/
import { Vec3, Tensor } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RuntimeContext } from '../../mol-task';
import { OrderedSet } from '../../mol-data/int';
import { PositionData } from './common';
import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4';
import { Box3D, GridLookup3D, fillGridDim } from '../../mol-math/geometry';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { OrderedSet } from '../../mol-data/int/ordered-set';
import { fillGridDim, PositionData } from './common';
import { Boundary } from './boundary';
import { GridLookup3D } from './lookup3d/grid';
import { Box3D } from './primitives/box3d';
import { Vec3 } from '../linear-algebra/3d/vec3';
import { Tensor } from '../linear-algebra/tensor';
import { Mat4 } from '../linear-algebra/3d/mat4';
function normalToLine(out: Vec3, p: Vec3) {
out[0] = out[1] = out[2] = 1.0;
@@ -48,8 +49,8 @@ function getAngleTables(probePositions: number): AnglesTables {
export const MolecularSurfaceCalculationParams = {
probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }, { description: 'Radius of the probe tracing the molecular surface.' }),
resolution: PD.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }, { description: 'Grid resolution/cell spacing.', ...BaseGeometry.CustomQualityParamInfo }),
probePositions: PD.Numeric(36, { min: 12, max: 90, step: 1 }, { description: 'Number of positions tested for probe target intersection.', ...BaseGeometry.CustomQualityParamInfo }),
resolution: PD.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }, { description: 'Grid resolution/cell spacing.' }),
probePositions: PD.Numeric(36, { min: 12, max: 90, step: 1 }, { description: 'Number of positions tested for probe target intersection.' }),
};
export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(MolecularSurfaceCalculationParams);
export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps

View File

@@ -6,7 +6,7 @@
*/
import { PositionData } from '../common';
import { OrderedSet } from '../../../mol-data/int';
import { OrderedSet } from '../../../mol-data/int/ordered-set';
import { Sphere3D } from './sphere3d';
import { Vec3 } from '../../linear-algebra/3d/vec3';
import { Mat4 } from '../../linear-algebra/3d/mat4';

View File

@@ -5,13 +5,15 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4, EPSILON } from '../../linear-algebra';
import { PositionData } from '../common';
import { OrderedSet } from '../../../mol-data/int';
import { OrderedSet } from '../../../mol-data/int/ordered-set';
import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
import { Box3D } from './box3d';
import { Axes3D } from './axes3d';
import { PrincipalAxes } from '../../linear-algebra/matrix/principal-axes';
import { Vec3 } from '../../linear-algebra/3d/vec3';
import { Mat4 } from '../../linear-algebra/3d/mat4';
import { EPSILON } from '../../linear-algebra/3d/common';
interface Sphere3D {
center: Vec3,

View File

@@ -5,7 +5,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { lerp as scalar_lerp } from '../../mol-math/interpolate';
import { lerp as scalar_lerp } from '../interpolate';
import { Mat3 } from '../linear-algebra/3d/mat3';
import { Mat4 } from '../linear-algebra/3d/mat4';
import { Quat } from '../linear-algebra/3d/quat';

View File

@@ -151,7 +151,7 @@ namespace Mat3 {
}
export function hasNaN(m: Mat3) {
for (let i = 0; i < 9; i++) if (isNaN(m[i])) return true;
for (let i = 0; i < 9; i++) if (Number.isNaN(m[i])) return true;
return false;
}

View File

@@ -111,7 +111,7 @@ namespace Mat4 {
}
export function hasNaN(m: Mat4) {
for (let i = 0; i < 16; i++) if (isNaN(m[i])) return true;
for (let i = 0; i < 16; i++) if (Number.isNaN(m[i])) return true;
return false;
}

View File

@@ -7,9 +7,9 @@
import { Mat4 } from './mat4';
import { Vec3 } from './vec3';
import { EVD } from '../matrix/evd';
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
import { Matrix } from '../matrix/matrix';
import { Sphere3D } from '../../geometry/primitives/sphere3d';
import { CentroidHelper } from '../../geometry/centroid-helper';
export { MinimizeRmsd };
namespace MinimizeRmsd {

View File

@@ -59,7 +59,7 @@ namespace Quat {
}
export function hasNaN(q: Quat) {
return isNaN(q[0]) || isNaN(q[1]) || isNaN(q[2]) || isNaN(q[3]);
return Number.isNaN(q[0]) || Number.isNaN(q[1]) || Number.isNaN(q[2]) || Number.isNaN(q[3]);
}
export function create(x: number, y: number, z: number, w: number) {

View File

@@ -55,7 +55,7 @@ namespace Vec2 {
}
export function hasNaN(a: Vec2) {
return isNaN(a[0]) || isNaN(a[1]);
return Number.isNaN(a[0]) || Number.isNaN(a[1]);
}
export function toArray<T extends NumberArray>(a: Vec2, out: T, offset: number) {

View File

@@ -52,8 +52,12 @@ export namespace Vec3 {
return _isFinite(a[0]) && _isFinite(a[1]) && _isFinite(a[2]);
}
export function isInteger(a: Vec3): boolean {
return Number.isInteger(a[0]) && Number.isInteger(a[1]) && Number.isInteger(a[2]);
}
export function hasNaN(a: Vec3) {
return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]);
return Number.isNaN(a[0]) || Number.isNaN(a[1]) || Number.isNaN(a[2]);
}
export function setNaN(out: Vec3) {

View File

@@ -19,9 +19,10 @@
import { Mat4 } from './mat4';
import { NumberArray } from '../../../mol-util/type-helpers';
import { Sphere3D } from '../../geometry/primitives/sphere3d';
import { EPSILON } from './common';
type SphereLike = { center: number[], radius: number };
interface Vec4 extends Array<number> { [d: number]: number, '@type': 'vec4', length: 4 }
function Vec4() {
@@ -54,7 +55,7 @@ namespace Vec4 {
return out;
}
export function fromSphere(out: Vec4, sphere: Sphere3D) {
export function fromSphere(out: Vec4, sphere: SphereLike) {
out[0] = sphere.center[0];
out[1] = sphere.center[1];
out[2] = sphere.center[2];
@@ -62,12 +63,12 @@ namespace Vec4 {
return out;
}
export function ofSphere(sphere: Sphere3D) {
export function ofSphere(sphere: SphereLike) {
return fromSphere(zero(), sphere);
}
export function hasNaN(a: Vec4) {
return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]) || isNaN(a[3]);
return Number.isNaN(a[0]) || Number.isNaN(a[1]) || Number.isNaN(a[2]) || Number.isNaN(a[3]);
}
export function toArray<T extends NumberArray>(a: Vec4, out: T, offset: number) {

View File

@@ -1,4 +1,3 @@
import { Mat3 } from './3d/mat3';
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
@@ -9,6 +8,7 @@ import { Mat3 } from './3d/mat3';
import { Mat4 } from './3d/mat4';
import { Vec3 } from './3d/vec3';
import { Vec4 } from './3d/vec4';
import { Mat3 } from './3d/mat3';
export interface Tensor { data: Tensor.Data, space: Tensor.Space }

View File

@@ -6,7 +6,6 @@
* @author Yana Rose <yana.v.rose@gmail.com>
*/
import { substringStartsWith } from '../../../mol-util/string';
import { CifCategory, CifField, CifFrame } from '../../../mol-io/reader/cif';
import { Tokenizer } from '../../../mol-io/reader/common/text/tokenizer';
import { PdbFile } from '../../../mol-io/reader/pdb/schema';
@@ -23,6 +22,16 @@ import { parseConect } from './conect';
import { isDebugMode } from '../../../mol-util/debug';
import { PdbHeaderData, addHeader } from './header';
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
import { StringLike } from '../../../mol-io/common/string-like';
function substringStartsWith(str: StringLike, start: number, end: number, target: string) {
const len = target.length;
if (len > end - start) return false;
for (let i = 0; i < len; i++) {
if (str.charCodeAt(start + i) !== target.charCodeAt(i)) return false;
}
return true;
}
export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
const { lines } = pdb;

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Volume } from '../../mol-model/volume';
import { Grid, Volume } from '../../mol-model/volume';
import { Task } from '../../mol-task';
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
@@ -71,19 +71,22 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
// always calculate stats when all stats related values are zero
const calcStats = header.AMIN === 0 && header.AMAX === 0 && header.AMEAN === 0 && header.ARMS === 0;
const volgrid: Grid = {
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3(), origin_frac, dimensions_frac)) },
cells: data,
stats: {
min: (Number.isNaN(header.AMIN) || calcStats) ? arrayMin(values) : header.AMIN,
max: (Number.isNaN(header.AMAX) || calcStats) ? arrayMax(values) : header.AMAX,
mean: (Number.isNaN(header.AMEAN) || calcStats) ? arrayMean(values) : header.AMEAN,
sigma: (Number.isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
},
};
return {
label: params?.label,
entryId: params?.entryId,
grid: {
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
cells: data,
stats: {
min: (isNaN(header.AMIN) || calcStats) ? arrayMin(values) : header.AMIN,
max: (isNaN(header.AMAX) || calcStats) ? arrayMax(values) : header.AMAX,
mean: (isNaN(header.AMEAN) || calcStats) ? arrayMean(values) : header.AMEAN,
sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
},
},
periodicity: Vec3.isInteger(dimensions_frac) ? 'xyz' : 'none',
grid: volgrid,
instances: [{ transform: Mat4.identity() }],
sourceData: Ccp4Format.create(source),
customProperties: new CustomProperties(),

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2018-2025 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 { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
@@ -38,6 +39,7 @@ export function volumeFromDensityServerData(source: DensityServer_Data_Database,
return {
label: params?.label,
entryId: params?.entryId,
periodicity: Vec3.isInteger(dimensions) ? 'xyz' : 'none',
grid: {
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
cells: data,

View File

@@ -36,6 +36,7 @@ export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, la
return {
label: params?.label,
entryId: params?.entryId,
periodicity: Vec3.isInteger(dimensions_frac) ? 'xyz' : 'none',
grid: {
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
cells: data,

View File

@@ -1,13 +1,14 @@
/**
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { UUID } from '../../../mol-util/uuid';
import { StructureSequence } from './properties/sequence';
import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
import { AtomicHierarchy, AtomicConformation, AtomicRanges, VdwRadius } from './properties/atomic';
import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from './properties/common';
import { CustomProperties } from '../../custom-property';
@@ -184,6 +185,20 @@ export namespace Model {
return center;
}
const AtomicRadiiProp = '__AtomicRadii__';
/** Get array of atomic radii for all atoms in the model (cached). */
export function getAtomicRadii(model: Model): Float32Array {
if (model._dynamicPropertyData[AtomicRadiiProp]) return model._dynamicPropertyData[AtomicRadiiProp];
const nAtoms = model.atomicHierarchy.atoms._rowCount;
const type_symbol = model.atomicHierarchy.atoms.type_symbol.value;
const radii = new Float32Array(nAtoms);
for (let i = 0; i < nAtoms; i++) {
radii[i] = VdwRadius(type_symbol(i));
}
model._dynamicPropertyData[AtomicRadiiProp] = radii;
return radii;
}
function invertIndex(xs: Column<number>) {
const invertedIndex = new Int32Array(xs.rowCount);
let isIdentity = false;

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025s mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -98,6 +98,9 @@ export const UnknownSaccharideComponent: SaccharideComponent = {
type: SaccharideType.Unknown
};
/**
* Unless stated otherwise, these follow SNFG recommendations.
*/
const Monosaccharides: SaccharideComponent[] = [
{ abbr: 'Glc', name: 'Glucose', color: SaccharideColors.Blue, type: SaccharideType.Hexose },
{ abbr: 'Man', name: 'Mannose', color: SaccharideColors.Green, type: SaccharideType.Hexose },
@@ -125,7 +128,7 @@ const Monosaccharides: SaccharideComponent[] = [
{ abbr: 'AllN', name: 'Allosamine', color: SaccharideColors.Purple, type: SaccharideType.Hexosamine },
{ abbr: 'TalN', name: 'Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.Hexosamine },
{ abbr: 'IdoN', name: 'Idosamine', color: SaccharideColors.Brown, type: SaccharideType.Hexosamine },
{ abbr: 'GlcNS', name: 'N-sulfo Glucosamine', color: SaccharideColors.Blue, type: SaccharideType.Hexosamine },
{ abbr: 'GlcNS', name: 'N-sulfo Glucosamine', color: SaccharideColors.Blue, type: SaccharideType.Hexosamine }, // from GLYCAM
{ abbr: 'GlcA', name: 'Glucuronic Acid', color: SaccharideColors.Blue, type: SaccharideType.Hexuronate },
{ abbr: 'ManA', name: 'Mannuronic Acid', color: SaccharideColors.Green, type: SaccharideType.Hexuronate },
@@ -148,7 +151,7 @@ const Monosaccharides: SaccharideComponent[] = [
{ abbr: '6dAltNAc', name: 'N-Acetyl 6-Deoxy Altrosamine', color: SaccharideColors.Pink, type: SaccharideType.DeoxyhexNAc },
{ abbr: '6dTalNAc', name: 'N-Acetyl 6-Deoxy Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.DeoxyhexNAc },
{ abbr: 'FucNAc', name: 'N-Acetyl Fucosamine', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc },
{ abbr: 'AAT', name: 'AAT', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc },
{ abbr: 'AAT', name: 'AAT', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc }, // from GLYCAM
{ abbr: 'Oli', name: 'Olivose', color: SaccharideColors.Blue, type: SaccharideType.DiDeoxyhexose },
{ abbr: 'Tyv', name: 'Tyvelose', color: SaccharideColors.Green, type: SaccharideType.DiDeoxyhexose },
@@ -181,8 +184,8 @@ const Monosaccharides: SaccharideComponent[] = [
{ abbr: 'MurNAc', name: 'N-Acetyl Muramic Acid', color: SaccharideColors.Purple, type: SaccharideType.Unknown },
{ abbr: 'MurNGc', name: 'N-Glycolyl Muramic Acid', color: SaccharideColors.LightBlue, type: SaccharideType.Unknown },
{ abbr: 'Mur', name: 'Muramic Acid', color: SaccharideColors.Brown, type: SaccharideType.Unknown },
{ abbr: 'K3O', name: 'D-glycero-a-D-talo-oct-2-Ulosonic Acid', color: SaccharideColors.Red, type: SaccharideType.Unknown },
{ abbr: 'dUA', name: '4-deoxy-4,5-didehydro iduronic acid', color: SaccharideColors.Secondary, type: SaccharideType.Unknown },
{ abbr: 'K3O', name: 'D-glycero-a-D-talo-oct-2-Ulosonic Acid', color: SaccharideColors.Red, type: SaccharideType.Unknown }, // from GLYCAM
{ abbr: 'dUA', name: '4-deoxy-4,5-didehydro iduronic acid', color: SaccharideColors.Secondary, type: SaccharideType.Unknown }, // from GLYCAM
{ abbr: 'Api', name: 'Apicose', color: SaccharideColors.Green, type: SaccharideType.Assigned },
{ abbr: 'Fru', name: 'Fructose', color: SaccharideColors.Green, type: SaccharideType.Assigned },

View File

@@ -3,12 +3,14 @@
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator';
import { Model } from '../model';
import { GridLookup3D, Lookup3D, Spacegroup } from '../../../mol-math/geometry';
import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds';
import { VdwRadius } from '../model/properties/atomic';
import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
import { BitFlags } from '../../../mol-util';
import { UnitRings } from './unit/rings';
@@ -46,7 +48,7 @@ namespace Unit {
export function create<K extends Kind>(id: number, invariantId: number, chainGroupId: number, traits: Traits, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, props?: K extends Kind.Atomic ? AtomicProperties : CoarseProperties): Unit {
switch (kind) {
case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, traits, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), props ?? AtomicProperties());
case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, traits, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, getAtomicRadiusFunc(model)), props ?? AtomicProperties());
case Kind.Spheres: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), props ?? CoarseProperties());
case Kind.Gaussians: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), props ?? CoarseProperties());
}
@@ -182,6 +184,11 @@ namespace Unit {
return {};
}
function getAtomicRadiusFunc(model: Model) {
const type_symbol = model.atomicHierarchy.atoms.type_symbol.value;
return (i: ElementIndex) => VdwRadius(type_symbol(i));
}
function getSphereRadiusFunc(model: Model) {
const r = model.coarseConformation.spheres.radius;
return (i: ElementIndex) => r[i];
@@ -270,7 +277,7 @@ namespace Unit {
}
const conformation = (this.model.atomicConformation !== model.atomicConformation || operator !== this.conformation.operator)
? SymmetryOperator.createMapping<ElementIndex>(operator, model.atomicConformation)
? SymmetryOperator.createMapping<ElementIndex>(operator, model.atomicConformation, getAtomicRadiusFunc(model))
: this.conformation;
return new Atomic(this.id, this.invariantId, this.chainGroupId, this.traits, model, this.elements, conformation, props);
}
@@ -278,9 +285,10 @@ namespace Unit {
get boundary() {
if (this.props.boundary) return this.props.boundary;
const { x, y, z } = this.model.atomicConformation;
const radius = Model.getAtomicRadii(this.model);
this.props.boundary = Traits.is(this.traits, Trait.FastBoundary)
? getFastBoundary({ x, y, z, indices: this.elements })
: getBoundary({ x, y, z, indices: this.elements });
? getFastBoundary({ x, y, z, radius, indices: this.elements })
: getBoundary({ x, y, z, radius, indices: this.elements });
return this.props.boundary;
}
@@ -456,11 +464,11 @@ namespace Unit {
get boundary() {
if (this.props.boundary) return this.props.boundary;
// TODO: support sphere radius?
const { x, y, z } = this.getCoarseConformation();
const radius = this.getCoarseRadii();
this.props.boundary = Traits.is(this.traits, Trait.FastBoundary)
? getFastBoundary({ x, y, z, indices: this.elements })
: getBoundary({ x, y, z, indices: this.elements });
? getFastBoundary({ x, y, z, radius, indices: this.elements })
: getBoundary({ x, y, z, radius, indices: this.elements });
return this.props.boundary;
}
@@ -494,6 +502,10 @@ namespace Unit {
return getCoarseConformation(this.kind, this.model);
}
private getCoarseRadii() {
return getCoarseRadii(this.kind, this.model);
}
constructor(id: number, invariantId: number, chainGroupId: number, traits: Traits, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) {
this.kind = kind;
this.objectPrimitive = kind === Kind.Spheres ? 'sphere' : 'gaussian';
@@ -514,6 +526,10 @@ namespace Unit {
return kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians;
}
function getCoarseRadii(kind: Kind, model: Model) {
return kind === Kind.Spheres ? model.coarseConformation.spheres.radius : undefined; // Zero radius for gaussians
}
interface CoarseProperties extends BaseProperties { }
function CoarseProperties(): CoarseProperties {

View File

@@ -31,7 +31,7 @@ namespace Grid {
export type Transform = { kind: 'spacegroup', cell: SpacegroupCell, fractionalBox: Box3D } | { kind: 'matrix', matrix: Mat4 }
const _scale = Mat4.zero(), _translate = Mat4.zero();
const _scale = Mat4(), _translate = Mat4();
export function getGridToCartesianTransform(grid: Grid) {
if (grid.transform.kind === 'matrix') {
return Mat4.copy(Mat4(), grid.transform.matrix);
@@ -39,9 +39,9 @@ namespace Grid {
if (grid.transform.kind === 'spacegroup') {
const { cells: { space } } = grid;
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3(), Box3D.size(Vec3(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
const translate = Mat4.fromTranslation(_translate, grid.transform.fractionalBox.min);
return Mat4.mul3(Mat4.zero(), grid.transform.cell.fromFractional, translate, scale);
return Mat4.mul3(Mat4(), grid.transform.cell.fromFractional, translate, scale);
}
return Mat4.identity();

View File

@@ -26,6 +26,7 @@ export interface Volume {
transform: Mat4
}>
readonly sourceData: ModelFormat
readonly periodicity?: 'none' | 'xyz'
// TODO use...
customProperties: CustomProperties

View File

@@ -4,23 +4,30 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Canvas3DParams } from '../../../mol-canvas3d/canvas3d';
import { PluginContext } from '../../../mol-plugin/context';
import { PluginState } from '../../../mol-plugin/state';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginStateSnapshotManager } from '../../manager/snapshots';
import { PluginStateAnimation } from '../model';
async function setPartialSnapshot(plugin: PluginContext, entry: Partial<PluginStateSnapshotManager.Entry['snapshot']>, first = false) {
if (entry.data) {
await plugin.runTask(plugin.state.data.setSnapshot(entry.data));
// update the canvas3d trackball with the snapshot
if (entry.canvas3d?.props?.trackball) {
plugin.canvas3d?.setProps({
trackball: entry.canvas3d?.props?.trackball
});
}
}
if (entry.canvas3d?.props) {
const settings = PD.normalizeParams(Canvas3DParams, entry.canvas3d.props, 'children');
if (entry.camera?.current || entry.camera?.focus) {
// Avoid multiple camera transitions (creates ugly cases when camera in old and new snapshot is the same)
settings.camera = undefined;
settings.cameraClipping = undefined;
settings.cameraFog = undefined;
}
plugin.canvas3d?.setProps(settings);
}
if (entry.camera?.current) {
plugin.canvas3d?.requestCameraReset({
snapshot: entry.camera.current,

View File

@@ -1,10 +1,11 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import type { EncodedFile } from '../../mol-io/common/binary-cif';
import { decodeMsgPack } from '../../mol-io/common/msgpack/decode';
import { StringLike } from '../../mol-io/common/string-like';
import { PluginContext } from '../../mol-plugin/context';
@@ -31,11 +32,16 @@ export function guessCifVariant(info: FileNameInfo, data: Uint8Array | StringLik
if (info.ext === 'bcif') {
try {
// TODO: find a way to run msgpackDecode only once
// now it is run twice, here and during file parsing
const { encoder } = decodeMsgPack(data as Uint8Array);
if (encoder.startsWith('VolumeServer')) return 'dscif';
// TODO: assumes volseg-volume-server only serves segments
if (encoder.startsWith('volseg-volume-server')) return 'segcif';
// now it is run twice, here and during file parsing
const file = decodeMsgPack(data as Uint8Array) as EncodedFile;
if (file.encoder.startsWith('VolumeServer')) return 'dscif';
// Assumes volseg-volume-server only serves segments
if (file.encoder.startsWith('volseg-volume-server')) return 'segcif';
if (bcifHasCategory(file, 'volume_data_3d_info')) {
if (bcifHasCategory(file, 'volume_data_3d')) return 'dscif';
if (bcifHasCategory(file, 'segmentation_data_3d')) return 'segcif';
}
} catch (e) {
console.error(e);
}
@@ -43,7 +49,26 @@ export function guessCifVariant(info: FileNameInfo, data: Uint8Array | StringLik
const str = data as StringLike;
if (str.startsWith('data_SERVER\n#\n_density_server_result')) return 'dscif';
if (str.startsWith('data_SERVER\n#\ndata_SEGMENTATION_DATA')) return 'segcif';
if (cifHasCategory(str, 'volume_data_3d_info')) {
if (cifHasCategory(str, 'volume_data_3d')) return 'dscif';
if (cifHasCategory(str, 'segmentation_data_3d')) return 'segcif';
}
if (str.includes('atom_site_fract_x') || str.includes('atom_site.fract_x')) return 'coreCif';
}
return -1;
}
function cifHasCategory(file: StringLike, categoryName: string): boolean {
return file.includes(`_${categoryName}.`);
}
function bcifHasCategory(file: EncodedFile, categoryName: string): boolean {
for (const block of file.dataBlocks) {
for (const category of block.categories) {
if (category.name === categoryName) return true;
}
}
return false;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -25,7 +25,7 @@ import { changeCameraRotation, structureLayingTransform } from './focus-camera/o
// TODO: make this customizable somewhere?
const DefaultCameraFocusOptions = {
minRadius: 5,
minRadius: 1,
extraRadius: 4,
durationMs: 250,
};

View File

@@ -195,6 +195,8 @@ const _Warning = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M1
export function WarningSvg() { return _Warning; }
const _ContentCut = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M 9.64 7.64 c 0.23 -0.5 0.36 -1.05 0.36 -1.64 c 0 -2.21 -1.79 -4 -4 -4 S 2 3.79 2 6 s 1.79 4 4 4 c 0.59 0 1.14 -0.13 1.64 -0.36 L 10 12 l -2.36 2.36 C 7.14 14.13 6.59 14 6 14 c -2.21 0 -4 1.79 -4 4 s 1.79 4 4 4 s 4 -1.79 4 -4 c 0 -0.59 -0.13 -1.14 -0.36 -1.64 L 12 14 l 7 7 h 3 v -1 L 9.64 7.64 Z M 6 8 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 0 12 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 6 -7.5 c -0.28 0 -0.5 -0.22 -0.5 -0.5 s 0.22 -0.5 0.5 -0.5 s 0.5 0.22 0.5 0.5 s -0.22 0.5 -0.5 0.5 Z M 19 3 l -6 6 l 2 2 l 7 -7 V 3 Z' /></svg>;
export function ContentCutSvg() { return _ContentCut; }
const _LightMode = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0 c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2 c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1 C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06 c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41 l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41 c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36 c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z' /></svg>;
export function LightModeSvg() { return _LightMode; }
// The following icons are adapted from https://icons.getbootstrap.com/ and
// licensed with https://github.com/twbs/bootstrap/blob/main/LICENSE

View File

@@ -418,12 +418,19 @@ export class TextControl extends SimpleParam<PD.Text> {
function TextCtrl({ props, placeholder, update }: { props: ParamProps<PD.Text>, placeholder: string, update: (v: string) => any }) {
const [value, setValue] = React.useState(props.value);
const [composition, setComposition] = React.useState(false);
React.useEffect(() => setValue(props.value), [props.value]);
const onChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (props.param.disableInteractiveUpdates) setValue(e.target.value);
if (composition || props.param.disableInteractiveUpdates) setValue(e.target.value);
else update(e.target.value);
};
const onCompositionEnd = (e: React.CompositionEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setComposition(false);
const target = e.target as EventTarget & (HTMLInputElement | HTMLTextAreaElement);
if (props.param.disableInteractiveUpdates) setValue(target.value);
else update(target.value);
};
const onBlur = (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (props.param.disableInteractiveUpdates) update(e.target.value);
};
@@ -443,13 +450,15 @@ function TextCtrl({ props, placeholder, update }: { props: ParamProps<PD.Text>,
return <div className='msp-control-text-area-wrapper'>
<textarea
value={value ?? ''} placeholder={placeholder} disabled={props.isDisabled}
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
onCompositionStart={() => setComposition(true)} onCompositionEnd={onCompositionEnd}
/>
</div>;
} else {
return <input type='text'
value={value ?? ''} placeholder={placeholder} disabled={props.isDisabled}
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
onCompositionStart={() => setComposition(true)} onCompositionEnd={onCompositionEnd}
/>;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -14,7 +14,7 @@ import { PluginConfig } from '../mol-plugin/config';
import { ParamDefinition as PD } from '../mol-util/param-definition';
import { PluginUIComponent } from './base';
import { Button, ControlGroup, IconButton } from './controls/common';
import { AspectRatioSvg, AutorenewSvg, BuildOutlinedSvg, CameraOutlinedSvg, CloseSvg, FullscreenSvg, HeadsetVRSvg, TuneSvg } from './controls/icons';
import { AspectRatioSvg, AutorenewSvg, BuildOutlinedSvg, CameraOutlinedSvg, CloseSvg, FullscreenSvg, HeadsetVRSvg, LightModeSvg, TuneSvg } from './controls/icons';
import { ToggleSelectionModeButton } from './structure/selection';
import { ViewportCanvas } from './viewport/canvas';
import { DownloadScreenshotControls } from './viewport/screenshot';
@@ -85,6 +85,19 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
}
};
toggleIllumination = () => {
if (!this.plugin.canvas3d) return;
PluginCommands.Canvas3D.SetSettings(this.plugin, {
settings: {
illumination: {
...this.plugin.canvas3d.props.illumination,
enabled: !this.plugin.canvas3d.props.illumination.enabled
}
}
});
};
setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { [p.name]: p.value } });
};
@@ -178,6 +191,7 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
&& this.plugin.config.get(PluginConfig.Viewport.ShowToggleFullscreen)
&& this.icon(AspectRatioSvg, this.toggleFullscreen, 'Toggle Full Screen', this.plugin.layout.state.expandToFullscreen)}
{this.plugin.config.get(PluginConfig.Viewport.ShowSettings) && this.icon(TuneSvg, this.toggleSettingsExpanded, 'Settings / Controls Info', this.state.isSettingsExpanded)}
{this.plugin.config.get(PluginConfig.Viewport.ShowIllumination) && this.icon(LightModeSvg, this.toggleIllumination, 'Illumination', this.plugin.canvas3d?.props.illumination.enabled || false)}
{xr && this.icon(HeadsetVRSvg, this.toggleXR, xrTitle, xrIsPresenting, !xrIsSupported)}
</div>
{this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode) && <div>

View File

@@ -190,13 +190,21 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
if (Binding.matchKey(b.keySpinAnimation, code, modifiers, key)) {
const name = tp.animate.name !== 'spin' ? 'spin' : 'off';
if (name === 'off') {
this.ctx.canvas3d.setProps({
trackball: { animate: { name, params: {} } }
PluginCommands.Canvas3D.SetSettings(this.ctx, {
settings: {
trackball: {
...tp,
animate: { name, params: {} }
}
}
});
} else {
this.ctx.canvas3d.setProps({
trackball: { animate: {
name, params: { speed: 1 } }
PluginCommands.Canvas3D.SetSettings(this.ctx, {
settings: {
trackball: {
...tp,
animate: { name, params: { speed: 0.1 } }
}
}
});
}
@@ -205,13 +213,21 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
if (Binding.matchKey(b.keyRockAnimation, code, modifiers, key)) {
const name = tp.animate.name !== 'rock' ? 'rock' : 'off';
if (name === 'off') {
this.ctx.canvas3d.setProps({
trackball: { animate: { name, params: {} } }
PluginCommands.Canvas3D.SetSettings(this.ctx, {
settings: {
trackball: {
...tp,
animate: { name, params: {} }
}
}
});
} else {
this.ctx.canvas3d.setProps({
trackball: { animate: {
name, params: { speed: 0.3, angle: 10 } }
PluginCommands.Canvas3D.SetSettings(this.ctx, {
settings: {
trackball: {
...tp,
animate: { name, params: { speed: 0.3, angle: 10 } }
}
}
});
}
@@ -220,8 +236,13 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
if (Binding.matchKey(b.keyToggleFlyMode, code, modifiers, key)) {
const flyMode = !tp.flyMode;
this.ctx.canvas3d.setProps({
trackball: { flyMode }
PluginCommands.Canvas3D.SetSettings(this.ctx, {
settings: {
trackball: {
...tp,
flyMode
}
}
});
if (this.ctx.canvas3dContext?.canvas) {
@@ -234,10 +255,12 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
}
if (Binding.matchKey(b.keyGlobalIllumination, code, modifiers, key)) {
this.ctx.canvas3d.setProps({
illumination: {
...ip,
enabled: !ip.enabled,
PluginCommands.Canvas3D.SetSettings(this.ctx, {
settings: {
illumination: {
...ip,
enabled: !ip.enabled
}
}
});
}

View File

@@ -61,6 +61,7 @@ export const PluginConfig = {
ShowAnimation: item('viewer.show-animation-button', true),
ShowTrajectoryControls: item('viewer.show-trajectory-controls', true),
ShowScreenshotControls: item('viewer.show-screenshot-controls', true),
ShowIllumination: item('viewer.show-illumination-button', true),
ShowXR: item<'auto' | 'always' | 'never'>('viewer.show-xr', 'always'),
},
Download: {

View File

@@ -89,7 +89,13 @@ class PluginState extends PluginComponent {
if (snapshot.behaviour) await this.plugin.runTask(this.behaviors.setSnapshot(snapshot.behaviour));
if (snapshot.data) await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
if (snapshot.canvas3d?.props) {
const settings = PD.normalizeParams(Canvas3DParams, snapshot.canvas3d.props, 'children');
const settings: Partial<Canvas3DProps> = PD.normalizeParams(Canvas3DParams, snapshot.canvas3d.props, 'children');
if (snapshot.camera?.current || snapshot.camera?.focus) {
// Avoid multiple camera transitions (creates ugly cases when camera in old and new snapshot is the same)
settings.camera = undefined;
settings.cameraClipping = undefined;
settings.cameraFog = undefined;
}
await PluginCommands.Canvas3D.SetSettings(this.plugin, { settings });
}
if (snapshot.canvas3dContext?.props) {

View File

@@ -247,7 +247,7 @@ function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, struct
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridDataDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
if (isTimingMode) webgl.timer.markEnd('createGaussianSurfaceTextureMesh');
const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, densityTextureData.maxRadius);
@@ -333,7 +333,7 @@ function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, structure
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridDataDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
if (isTimingMode) webgl.timer.markEnd('createStructureGaussianSurfaceTextureMesh');
const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, densityTextureData.maxRadius);

View File

@@ -21,13 +21,19 @@ import { MeshValues } from '../../../mol-gl/renderable/mesh';
import { Texture } from '../../../mol-gl/webgl/texture';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
import { BaseGeometry, ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
import { ValueCell } from '../../../mol-util';
import { ComplexMeshVisual, ComplexVisual } from '../complex-visual';
const CommonMolecularSurfaceCalculationParams = {
...MolecularSurfaceCalculationParams,
resolution: { ...MolecularSurfaceCalculationParams.resolution, ...BaseGeometry.CustomQualityParamInfo },
probePositions: { ...MolecularSurfaceCalculationParams.probePositions, ...BaseGeometry.CustomQualityParamInfo },
};
export const MolecularSurfaceMeshParams = {
...UnitsMeshParams,
...MolecularSurfaceCalculationParams,
...CommonMolecularSurfaceCalculationParams,
...CommonSurfaceParams,
...ColorSmoothingParams,
};

View File

@@ -3,7 +3,7 @@
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Segmentation } from '../../../../../mol-data/int';
import { Segmentation } from '../../../../../mol-data/int/segmentation';
import { SortedRanges } from '../../../../../mol-data/int/sorted-ranges';
import { ElementIndex, ResidueIndex, Unit } from '../../../../../mol-model/structure';
import { MoleculeType } from '../../../../../mol-model/structure/model/types';

View File

@@ -6,7 +6,6 @@
*/
import { Unit, StructureElement, ElementIndex, ResidueIndex, Structure } from '../../../../../mol-model/structure';
import { Segmentation, SortedArray } from '../../../../../mol-data/int';
import { MoleculeType, SecondaryStructureType } from '../../../../../mol-model/structure/model/types';
import { Iterator } from '../../../../../mol-data/iterator';
import { Vec3 } from '../../../../../mol-math/linear-algebra';
@@ -17,6 +16,8 @@ import { AtomicConformation } from '../../../../../mol-model/structure/model/pro
import { SecondaryStructureProvider } from '../../../../../mol-model-props/computed/secondary-structure';
import { HelixOrientationProvider } from '../../../../../mol-model-props/computed/helix-orientation';
import { SecondaryStructure } from '../../../../../mol-model/structure/model/properties/secondary-structure';
import { Segmentation } from '../../../../../mol-data/int/segmentation';
import { SortedArray } from '../../../../../mol-data/int/sorted-array';
function isHelixSS(ss: SecondaryStructureType.Flag) {
return SecondaryStructureType.is(ss, SecondaryStructureType.Flag.Helix);

View File

@@ -11,6 +11,7 @@ import { Box3D, SpacegroupCell } from '../mol-math/geometry';
import { ModelSymmetry } from '../mol-model-formats/structure/property/symmetry';
import { Volume } from '../mol-model/volume';
import { Location } from '../mol-model/location';
import { isStandaloneHmd } from '../mol-util/browser';
export interface VisualUpdateState {
updateTransform: boolean
@@ -76,6 +77,28 @@ export const DefaultQualityThresholds = {
};
export type QualityThresholds = typeof DefaultQualityThresholds
enum QualityLevel {
Lowest,
Lower,
Low,
Medium,
High,
Higher,
Highest
}
function visualQualityToLevel(quality: Exclude<VisualQuality, 'auto' | 'custom'>): QualityLevel {
switch (quality) {
case 'lowest': return QualityLevel.Lowest;
case 'lower': return QualityLevel.Lower;
case 'low': return QualityLevel.Low;
case 'medium': return QualityLevel.Medium;
case 'high': return QualityLevel.High;
case 'higher': return QualityLevel.Higher;
case 'highest': return QualityLevel.Highest;
}
}
export function getStructureQuality(structure: Structure, tresholds: Partial<QualityThresholds> = {}): VisualQuality {
const t = { ...DefaultQualityThresholds, ...tresholds };
let score = structure.elementCount * t.elementCountFactor;
@@ -132,73 +155,77 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
}
}
switch (quality) {
case 'highest':
detail = 3;
radialSegments = 36;
linearSegments = 18;
resolution = 0.1;
imageResolution = 0.01;
probePositions = 72;
doubleSided = true;
break;
case 'higher':
detail = 3;
radialSegments = 28;
linearSegments = 14;
resolution = 0.3;
imageResolution = 0.05;
probePositions = 48;
doubleSided = true;
break;
case 'high':
detail = 2;
radialSegments = 20;
linearSegments = 10;
resolution = 0.5;
imageResolution = 0.1;
probePositions = 36;
doubleSided = true;
break;
case 'medium':
detail = 1;
radialSegments = 12;
linearSegments = 8;
resolution = 0.8;
imageResolution = 0.2;
probePositions = 24;
doubleSided = true;
break;
case 'low':
detail = 0;
radialSegments = 8;
linearSegments = 3;
resolution = 1.3;
imageResolution = 0.4;
probePositions = 24;
doubleSided = false;
break;
case 'lower':
detail = 0;
radialSegments = 4;
linearSegments = 2;
resolution = 3;
imageResolution = 0.7;
probePositions = 12;
doubleSided = false;
break;
case 'lowest':
detail = 0;
radialSegments = 2;
linearSegments = 1;
resolution = 8;
imageResolution = 1;
probePositions = 12;
doubleSided = false;
break;
case 'custom':
// use defaults or given props as set above
break;
if (quality !== 'custom' && quality !== 'auto') {
let level = visualQualityToLevel(quality);
if (isStandaloneHmd()) {
level = Math.max(level - 1, QualityLevel.Lowest);
}
switch (level) {
case QualityLevel.Highest:
detail = 3;
radialSegments = 36;
linearSegments = 18;
resolution = 0.1;
imageResolution = 0.01;
probePositions = 72;
doubleSided = true;
break;
case QualityLevel.Higher:
detail = 3;
radialSegments = 28;
linearSegments = 14;
resolution = 0.3;
imageResolution = 0.05;
probePositions = 48;
doubleSided = true;
break;
case QualityLevel.High:
detail = 2;
radialSegments = 20;
linearSegments = 10;
resolution = 0.5;
imageResolution = 0.1;
probePositions = 36;
doubleSided = true;
break;
case QualityLevel.Medium:
detail = 1;
radialSegments = 12;
linearSegments = 8;
resolution = 0.8;
imageResolution = 0.2;
probePositions = 24;
doubleSided = true;
break;
case QualityLevel.Low:
detail = 0;
radialSegments = 8;
linearSegments = 3;
resolution = 1.3;
imageResolution = 0.4;
probePositions = 24;
doubleSided = false;
break;
case QualityLevel.Lower:
detail = 0;
radialSegments = 4;
linearSegments = 2;
resolution = 3;
imageResolution = 0.7;
probePositions = 12;
doubleSided = false;
break;
case QualityLevel.Lowest:
detail = 0;
radialSegments = 2;
linearSegments = 1;
resolution = 8;
imageResolution = 1;
probePositions = 12;
doubleSided = false;
break;
}
}
// max resolution based on volume (for 'auto' quality)

View File

@@ -18,7 +18,7 @@ import { calculateTransformBoundingSphere } from '../mol-gl/renderable/util';
import { ValueCell } from '../mol-util';
import { Overpaint } from '../mol-theme/overpaint';
import { createOverpaint, clearOverpaint, applyOverpaintColor } from '../mol-geo/geometry/overpaint-data';
import { Interval } from '../mol-data/int';
import { Interval } from '../mol-data/int/interval';
import { Transparency } from '../mol-theme/transparency';
import { createTransparency, clearTransparency, applyTransparencyValue, getTransparencyAverage, getTransparencyMin } from '../mol-geo/geometry/transparency-data';
import { Clipping } from '../mol-theme/clipping';

View File

@@ -16,11 +16,12 @@ import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
import { VisualUpdateState } from '../util';
import { RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Interval, OrderedSet } from '../../mol-data/int';
import { Loci, EmptyLoci } from '../../mol-model/loci';
import { PickingId } from '../../mol-geo/geometry/picking';
import { createVolumeCellLocationIterator, createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
import { Texture } from '../../mol-gl/webgl/texture';
import { Interval } from '../../mol-data/int/interval';
import { OrderedSet } from '../../mol-data/int/ordered-set';
function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
const bbox = Box3D();

View File

@@ -15,7 +15,6 @@ import { VisualUpdateState } from '../util';
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
import { PickingId } from '../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../mol-model/loci';
import { Interval, OrderedSet } from '../../mol-data/int';
import { createVolumeCellLocationIterator, eachVolumeLoci } from './util';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { BaseGeometry } from '../../mol-geo/geometry/base';
@@ -28,6 +27,8 @@ import { sphereVertexCount } from '../../mol-geo/primitive/sphere';
import { Points } from '../../mol-geo/geometry/points/points';
import { PointsBuilder } from '../../mol-geo/geometry/points/points-builder';
import { Mat4 } from '../../mol-math/linear-algebra';
import { Interval } from '../../mol-data/int/interval';
import { OrderedSet } from '../../mol-data/int/ordered-set';
export const VolumeDotParams = {
isoValue: Volume.IsoValueParam,

View File

@@ -17,10 +17,9 @@ import { Lines } from '../../mol-geo/geometry/lines/lines';
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
import { PickingId } from '../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../mol-model/loci';
import { Interval, OrderedSet } from '../../mol-data/int';
import { Tensor, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { fillSerial } from '../../mol-util/array';
import { createVolumeCellLocationIterator, createVolumeTexture2d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
import { createVolumeCellLocationIterator, createVolumeTexture2d, createWrappedVolume, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
import { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurface';
import { WebGLContext } from '../../mol-gl/webgl/context';
@@ -28,25 +27,34 @@ import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
import { Texture } from '../../mol-gl/webgl/texture';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { ValueCell } from '../../mol-util/value-cell';
import { Interval } from '../../mol-data/int/interval';
import { OrderedSet } from '../../mol-data/int/ordered-set';
export const VolumeIsosurfaceParams = {
isoValue: Volume.IsoValueParam,
wrap: PD.Select('auto', PD.arrayToOptions(['off', 'on', 'auto'] as const)),
};
export type VolumeIsosurfaceParams = typeof VolumeIsosurfaceParams
export type VolumeIsosurfaceProps = PD.Values<VolumeIsosurfaceParams>
export const VolumeIsosurfaceTextureParams = {
isoValue: Volume.IsoValueParam,
...VolumeIsosurfaceParams,
tryUseGpu: PD.Boolean(true),
gpuDataType: PD.Select('byte', PD.arrayToOptions(['byte', 'float', 'halfFloat'] as const), { hideIf: p => !p.tryUseGpu }),
};
export type VolumeIsosurfaceGpuParams = typeof VolumeIsosurfaceTextureParams
export type VolumeIsosurfaceGpuProps = PD.Values<VolumeIsosurfaceGpuParams>
export type VolumeIsosurfaceTextureParams = typeof VolumeIsosurfaceTextureParams
export type VolumeIsosurfaceTextureProps = PD.Values<VolumeIsosurfaceTextureParams>
function gpuSupport(webgl: WebGLContext) {
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
}
function shouldWrap(volume: Volume, wrap: VolumeIsosurfaceProps['wrap']) {
if (wrap === 'on') return true;
if (wrap === 'off') return false;
return volume.periodicity === 'xyz';
}
const Padding = 1;
function suitableForGpu(volume: Volume, webgl: WebGLContext) {
@@ -97,12 +105,17 @@ export function eachIsosurface(loci: Loci, volume: Volume, key: number, props: V
export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceProps, mesh?: Mesh) {
ctx.runtime.update({ message: 'Marching cubes...' });
let cells = volume.grid.cells;
if (shouldWrap(volume, props.wrap)) {
cells = createWrappedVolume(volume).grid.cells;
}
const ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
const surface = await computeMarchingCubesMesh({
isoLevel: Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue,
scalarField: volume.grid.cells,
idField: Tensor.create(volume.grid.cells.space, Tensor.Data1(ids))
scalarField: cells,
idField: Tensor.create(cells.space, Tensor.Data1(ids))
}, mesh).runAsChild(ctx.runtime);
const transform = Grid.getGridToCartesianTransform(volume.grid);
@@ -138,7 +151,10 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
getLoci: getIsosurfaceLoci,
eachLocation: eachIsosurface,
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
state.createGeometry = (
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
newProps.wrap !== currentProps.wrap
);
},
geometryUtils: Mesh.Utils,
mustRecreate: (volumekey: VolumeKey, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
@@ -155,11 +171,15 @@ namespace VolumeIsosurfaceTexture {
export function clear(volume: Volume) {
delete volume._propertyData[name];
}
export function get(volume: Volume, webgl: WebGLContext, props: VolumeIsosurfaceGpuProps) {
export function get(volume: Volume, webgl: WebGLContext, props: VolumeIsosurfaceTextureProps) {
const { gpuDataType } = props;
const wrap = shouldWrap(volume, props.wrap);
const transform = Grid.getGridToCartesianTransform(volume.grid);
const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
const gridTexDim = Vec3.create(width, height, 0);
const gridDataDim = Vec3.subScalar(Vec3(), gridDimension, wrap ? 1 : 0);
const gridTexScale = Vec2.create(width / texDim, height / texDim);
// console.log({ texDim, width, height, gridDimension });
@@ -167,15 +187,15 @@ namespace VolumeIsosurfaceTexture {
throw new Error('volume too large for gpu isosurface extraction');
}
const dataType = props.gpuDataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : props.gpuDataType;
const dataType = gpuDataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : gpuDataType;
if (volume._propertyData[name]?.dataType !== dataType) {
if (volume._propertyData[name]?.dataType !== dataType || volume._propertyData[name]?.wrap !== wrap) {
const texture = dataType === 'byte'
? webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'linear')
: dataType === 'halfFloat'
? webgl.resources.texture('image-float16', 'alpha', 'fp16', 'linear')
: webgl.resources.texture('image-float32', 'alpha', 'float', 'linear');
volume._propertyData[name] = { texture, dataType };
volume._propertyData[name] = { texture, dataType, wrap };
texture.define(texDim, texDim);
// load volume into sub-section of texture
texture.load(createVolumeTexture2d(volume, 'data', Padding, dataType), true);
@@ -191,12 +211,13 @@ namespace VolumeIsosurfaceTexture {
transform,
gridDimension,
gridTexDim,
gridDataDim,
gridTexScale
};
}
}
function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceGpuProps, textureMesh?: TextureMesh) {
function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceTextureProps, textureMesh?: TextureMesh) {
const { webgl } = ctx;
if (!webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
@@ -204,6 +225,10 @@ function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, k
return TextureMesh.createEmpty(textureMesh);
}
if (shouldWrap(volume, props.wrap)) {
volume = createWrappedVolume(volume);
}
const { max, min } = volume.grid.stats;
const diff = max - min;
const value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
@@ -214,10 +239,10 @@ function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, k
const boundingSphere = Volume.getBoundingSphere(volume); // getting isosurface bounding-sphere is too expensive here
const create = (textureMesh?: TextureMesh) => {
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, webgl, props);
const { texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, webgl, props);
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(webgl, texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
return TextureMesh.create(gv.vertexCount, groupCount, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
};
@@ -240,8 +265,11 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
getLoci: getIsosurfaceLoci,
eachLocation: eachIsosurface,
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
if (newProps.gpuDataType !== currentProps.gpuDataType) state.createGeometry = true;
state.createGeometry = (
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
newProps.gpuDataType !== currentProps.gpuDataType ||
newProps.wrap !== currentProps.wrap
);
},
geometryUtils: TextureMesh.Utils,
mustRecreate: (volumeKey: VolumeKey, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
@@ -261,12 +289,17 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceProps, lines?: Lines) {
ctx.runtime.update({ message: 'Marching cubes...' });
let cells = volume.grid.cells;
if (shouldWrap(volume, props.wrap)) {
cells = createWrappedVolume(volume).grid.cells;
}
const ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
const wireframe = await computeMarchingCubesLines({
isoLevel: Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue,
scalarField: volume.grid.cells,
idField: Tensor.create(volume.grid.cells.space, Tensor.Data1(ids))
scalarField: cells,
idField: Tensor.create(cells.space, Tensor.Data1(ids))
}, lines).runAsChild(ctx.runtime);
const transform = Grid.getGridToCartesianTransform(volume.grid);
@@ -293,7 +326,10 @@ export function IsosurfaceWireframeVisual(materialId: number): VolumeVisual<Isos
getLoci: getIsosurfaceLoci,
eachLocation: eachIsosurface,
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
state.createGeometry = (
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
newProps.wrap !== currentProps.wrap
);
},
geometryUtils: Lines.Utils
}, materialId);

View File

@@ -14,7 +14,6 @@ import { createTransform, TransformData } from '../../mol-geo/geometry/transform
import { createRenderObject, getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
import { PickingId } from '../../mol-geo/geometry/picking';
import { Loci, isEveryLoci, EmptyLoci, isEmptyLoci } from '../../mol-model/loci';
import { Interval, OrderedSet } from '../../mol-data/int';
import { getQualityProps, LocationCallback, VisualUpdateState } from '../util';
import { ColorTheme } from '../../mol-theme/color';
import { ValueCell } from '../../mol-util';
@@ -37,6 +36,8 @@ import { createMarkers } from '../../mol-geo/geometry/marker-data';
import { Emissive } from '../../mol-theme/emissive';
import { SizeTheme } from '../../mol-theme/size';
import { Sphere3D } from '../../mol-math/geometry/primitives/sphere3d';
import { Interval } from '../../mol-data/int/interval';
import { OrderedSet } from '../../mol-data/int/ordered-set';
export type VolumeKey = { volume: Volume, key: number }
export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeKey, P> { }

View File

@@ -16,7 +16,6 @@ import { VisualUpdateState } from '../util';
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
import { PickingId } from '../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../mol-model/loci';
import { Interval, OrderedSet, SortedArray } from '../../mol-data/int';
import { Mat4, Tensor, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { fillSerial } from '../../mol-util/array';
import { createSegmentTexture2d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
@@ -26,6 +25,9 @@ import { BaseGeometry } from '../../mol-geo/geometry/base';
import { ValueCell } from '../../mol-util/value-cell';
import { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurface';
import { Box3D } from '../../mol-math/geometry/primitives/box3d';
import { SortedArray } from '../../mol-data/int/sorted-array';
import { Interval } from '../../mol-data/int/interval';
import { OrderedSet } from '../../mol-data/int/ordered-set';
export const VolumeSegmentParams = {
segments: PD.Converted(
@@ -222,6 +224,7 @@ function getSegmentTexture(volume: Volume, segment: Volume.SegmentIndex, webgl:
const gridDimension = Box3D.size(Vec3(), bbox);
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
const gridTexDim = Vec3.create(width, height, 0);
const gridDataDim = Vec3.clone(gridDimension);
const gridTexScale = Vec2.create(width / texDim, height / texDim);
// console.log({ texDim, width, height, gridDimension });
@@ -247,6 +250,7 @@ function getSegmentTexture(volume: Volume, segment: Volume.SegmentIndex, webgl:
transform,
gridDimension,
gridTexDim,
gridDataDim,
gridTexScale
};
}
@@ -258,11 +262,11 @@ async function createVolumeSegmentTextureMesh(ctx: VisualContext, volume: Volume
return TextureMesh.createEmpty(textureMesh);
}
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = getSegmentTexture(volume, segment, ctx.webgl);
const { texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform } = getSegmentTexture(volume, segment, ctx.webgl);
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
const buffer = textureMesh?.doubleBuffer.get();
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, 0.5, false, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform, 0.5, false, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
const groupCount = volume.grid.cells.data.length;
const instances = Interval.ofLength(volume.instances.length as Volume.InstanceIndex);

Some files were not shown because too many files have changed in this diff Show More