Compare commits

...

42 Commits

Author SHA1 Message Date
dsehnal
7fac8a8f77 2.0.5 2021-04-26 16:02:31 +02:00
dsehnal
7266c67e32 2.0.5 changelog 2021-04-26 16:00:31 +02:00
dsehnal
50c8d09742 default camera radius set to 0 2021-04-26 15:13:46 +02:00
dsehnal
7377947975 Changelog 2021-04-25 15:15:26 +02:00
David Sehnal
a3c4daf30a Merge pull request #169 from sukolsak/texture-mesh-export
Add TextureMesh export support
2021-04-25 15:13:26 +02:00
Sukolsak Sakshuwong
9d7e6f1d99 add TextureMesh export support 2021-04-25 05:04:03 -07:00
dsehnal
9e105020e3 lazy volume loading 2021-04-25 12:10:09 +02:00
Alexander Rose
93df548cfe add torus primitive and fix render tests 2021-04-23 22:55:20 -07:00
Alexander Rose
a0b1593c82 add MeshBuilder.addMesh 2021-04-23 22:12:19 -07:00
Alexander Rose
fc81e08d73 Support full pausing (no draw) rendering 2021-04-23 22:10:01 -07:00
Alexander Rose
5369fa5adf canvas viewport support fixes and improvements
- restrict ssao to viewport bounds
- only downscale ssao buffer (not upscale)
- avoid zero camera radius/radiusMax to allow camera movements in empty scenes and to avoid ssao artifacts
2021-04-23 22:07:34 -07:00
dsehnal
316a77c716 guard against non-invertible matrices in Camera.update
+ relative viewports and dynamic updating of them sometimes caused non-invertible matrix
2021-04-23 19:05:29 +02:00
dsehnal
42dfa69ad7 Residue list selection helper 2021-04-21 21:24:28 +02:00
dsehnal
cae4eb8b0e await screenshot clipboard write & fallback to <img> on fail 2021-04-21 20:08:37 +02:00
David Sehnal
5514b24fdf Merge pull request #167 from molstar/multi-canvas3d
Multi-canvas support for PluginContext
2021-04-21 20:05:05 +02:00
dsehnal
d570bc352e "relative" canvas3d viewport and picking dimensions fix 2021-04-20 20:26:13 +02:00
dsehnal
8a76a3fa64 2.0.4 2021-04-20 11:23:23 +02:00
dsehnal
71bf4e21f5 changelog 2021-04-20 11:21:29 +02:00
dsehnal
e0d36c30d3 Fix measurement labels & interactions 2021-04-20 11:19:15 +02:00
David Sehnal
b53debcfef Merge pull request #163 from sukolsak/fix-zip-progress
Fix incorrect deflate progress
2021-04-14 15:02:15 +02:00
Sukolsak Sakshuwong
d0705ac226 fix deflate progress 2021-04-14 05:07:05 -07:00
Alexander Rose
e01eacb3fe changelog 2021-04-13 23:12:39 -07:00
Alexander Rose
d4102b476b Fix, read SDF multi-line values 2021-04-13 23:07:55 -07:00
dsehnal
83ce17174a changelog 2021-04-13 21:17:28 +02:00
dsehnal
18023d7f26 Merge branch 'master' of https://github.com/molstar/molstar 2021-04-13 21:16:28 +02:00
dsehnal
a8541d5967 Structure.eachAtomicHierarchyElement 2021-04-13 21:15:44 +02:00
David Sehnal
8b21818f2e Merge pull request #159 from sukolsak/obj-export
OBJ Export
2021-04-13 17:06:48 +02:00
Sukolsak Sakshuwong
0b290247dc show partial progress when exporting large meshes 2021-04-11 13:15:50 -07:00
Sukolsak Sakshuwong
fb5010e962 reorder arguments to addMeshWithColors() 2021-04-10 18:12:32 -07:00
Sukolsak Sakshuwong
178789d327 make RenderObjectExporter.add() async 2021-04-10 18:10:42 -07:00
Sukolsak Sakshuwong
4fae526073 wip 2021-04-09 15:59:14 -07:00
dsehnal
05f1d8085a 2.0.3 2021-04-09 17:47:12 +02:00
dsehnal
38bbabd742 fix test 2021-04-09 17:45:51 +02:00
dsehnal
3ab958a93c changelog 2021-04-09 17:43:23 +02:00
dsehnal
f59d589a30 CubeGridFormat 2021-04-09 17:41:13 +02:00
David Sehnal
11f7e54704 Merge pull request #158 from molstar/gradient-themes
Add ColorTheme.palette support
2021-04-09 10:01:59 +02:00
dsehnal
16ebd8266e changelog 2021-04-09 10:01:21 +02:00
dsehnal
7a796a4d3d Merge branch 'master' of https://github.com/molstar/molstar into gradient-themes 2021-04-09 09:59:46 +02:00
Alexander Rose
1cbb915962 started a changelog 2021-04-08 23:14:55 -07:00
Alexander Rose
80486d58c3 async deflate (and zip) 2021-04-08 23:13:30 -07:00
dsehnal
81bc116c4d TextureImage.filter 2021-04-08 09:49:35 +02:00
dsehnal
4249064dd1 Add ColorTheme.palette support
- add example to basic-wrapper that uses it
2021-04-07 15:55:54 +02:00
61 changed files with 1840 additions and 259 deletions

58
CHANGELOG.md Normal file
View File

@@ -0,0 +1,58 @@
# Change Log
All notable changes to this project will be documented in this file, following the suggestions of [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic Versioning](http://semver.org/) for its most widely used - and defacto - public interfaces.
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
- [empty]
## [v2.0.5] - 2021-04-26
- Ability to pass ``Canvas3DContext`` to ``PluginContext.fromCanvas``.
- Relative frame support for ``Canvas3D`` viewport.
- Fix bug in screenshot copy UI.
- Add ability to select residues from a list of identifiers to the Selection UI.
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
- Add `MeshBuilder.addMesh`.
- Add `Torus` primitive.
- Lazy volume loading support.
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
- Add ``TextureMesh`` support to ``geo-export`` extension.
## [v2.0.4] - 2021-04-20
- [WIP] Mesh export extension
- ``Structure.eachAtomicHierarchyElement`` (#161)
- Fixed reading multi-line values in SDF format
- Fixed Measurements UI labels (#166)
## [v2.0.3] - 2021-04-09
### Added
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
### Changed
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- `Canvas3D.getRenderObjects`.
- [WIP] Animate state interpolating, including model trajectories
### Changed
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
### Fixed
- VolumeFromDensityServerCif transform label
## [v2.0.1] - 2021-03-23
### Fixed
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
## [v2.0.0] - 2021-03-23
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

4
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "molstar",
"version": "2.0.2",
"version": "2.0.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "2.0.2",
"version": "2.0.5",
"license": "MIT",
"dependencies": {
"@types/argparse": "^1.0.38",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.0.2",
"version": "2.0.5",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

@@ -21,7 +21,7 @@
<script type="text/javascript" src="./molstar.js"></script>
<script type="text/javascript">
var viewer = new molstar.Viewer('app', {
layoutIsExpanded: false,
layoutIsExpanded: true,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
@@ -37,6 +37,20 @@
});
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadVolumeFromUrl({
// url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
// format: 'dscif',
// isBinary: true
// }, [{
// type: 'relative',
// value: 1,
// color: 0x3377aa
// }], {
// entryId: 'EMD-30210',
// isLazy: true
// });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
</script>
</body>

View File

@@ -10,6 +10,7 @@ import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Mp4Export } from '../../extensions/mp4-export';
import { GeometryExport } from '../../extensions/geo-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
@@ -55,7 +56,8 @@ const Extensions = {
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport)
};
const DefaultViewerOptions = {
@@ -241,17 +243,29 @@ export class Viewer {
}));
}
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string, isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, ...v }))
});
return update.commit();
}
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: options?.entryId }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
@@ -259,7 +273,7 @@ export class Viewer {
for (const iso of isovalues) {
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));

View File

@@ -0,0 +1,51 @@
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
import { Vec3 } from '../../mol-math/linear-algebra';
import { ColorTheme } from '../../mol-theme/color';
import { ThemeDataContext } from '../../mol-theme/theme';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export function CustomColorTheme(
ctx: ThemeDataContext,
props: PD.Values<{}>
): ColorTheme<{}> {
const { radius, center } = ctx.structure?.boundary.sphere!;
const radiusSq = Math.max(radius * radius, 0.001);
const scale = ColorTheme.PaletteScale;
return {
factory: CustomColorTheme,
granularity: 'vertex',
color: location => {
if (!isPositionLocation(location)) return ColorNames.black;
const dist = Vec3.squaredDistance(location.position, center);
const t = Math.min(dist / radiusSq, 1);
return ((t * scale) | 0) as Color;
},
palette: {
filter: 'nearest',
colors: [
ColorNames.red,
ColorNames.pink,
ColorNames.violet,
ColorNames.orange,
ColorNames.yellow,
ColorNames.green,
ColorNames.blue
]
},
props: props,
description: '',
};
}
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
name: 'basic-wrapper-custom-color-theme',
label: 'Custom Color Theme',
category: ColorTheme.Category.Misc,
factory: CustomColorTheme,
getParams: () => ({}),
defaultValues: { },
isApplicable: (ctx: ThemeDataContext) => true,
};

View File

@@ -97,6 +97,7 @@
addHeader('Misc');
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme());
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
addHeader('Interactivity');

View File

@@ -18,6 +18,7 @@ import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { StripedResidues } from './coloring';
import { CustomToastMessage } from './controls';
import { CustomColorThemeProvider } from './custom-theme';
import './index.html';
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
require('mol-plugin-ui/skin/light.scss');
@@ -42,6 +43,7 @@ class BasicWrapper {
});
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
}
@@ -103,6 +105,13 @@ class BasicWrapper {
}
});
},
applyCustomTheme: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: CustomColorThemeProvider.name as any });
}
});
},
applyDefault: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {

View File

@@ -9,6 +9,7 @@ import { Grid } from '../../mol-model/volume';
import { SphericalBasisOrder } from './spherical-functions';
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
import { ModelFormat } from '../../mol-model-formats/format';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
@@ -59,6 +60,17 @@ export interface CubeGrid {
isovalues?: { negative?: number; positive?: number };
}
export type CubeGridFormat = ModelFormat<CubeGrid>;
// eslint-disable-next-line
export function CubeGridFormat(grid: CubeGrid): CubeGridFormat {
return { name: 'custom grid', kind: 'cube-grid', data: grid };
}
export function isCubeGridData(f: ModelFormat): f is CubeGridFormat {
return f.kind === 'cube-grid';
}
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const geometry = params.basis.atoms.map(a => a.center);
const { gridSpacing: spacing, boxExpand: expand } = params;

View File

@@ -17,7 +17,7 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
import { StateTransformer } from '../../mol-state';
import { Theme } from '../../mol-theme/theme';
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
import { AlphaOrbital, Basis, CubeGrid, CubeGridFormat, isCubeGridData } from './data-model';
import { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
@@ -114,7 +114,7 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
sourceData: CubeGridFormat(data),
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
@@ -146,7 +146,7 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
sourceData: CubeGridFormat(data),
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
@@ -210,9 +210,9 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
});
function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Data, params: StateTransformer.Params<typeof CreateOrbitalRepresentation3D>) {
if (volume.data.sourceData.kind !== 'alpha-orbitals') throw new Error('Invalid data source kind.');
if (!isCubeGridData(volume.data.sourceData)) throw new Error('Invalid data source kind.');
const { isovalues } = volume.data.sourceData.data as CubeGrid;
const { isovalues } = volume.data.sourceData.data;
if (!isovalues) throw new Error('Isovalues are not computed.');
const value = isovalues[params.kind];

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { ObjExporter } from './export';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateSelection } from '../../mol-state';
import { SetUtils } from '../../mol-util/set';
import { zip } from '../../mol-util/zip/zip';
export class GeometryControls extends PluginComponent {
getFilename() {
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
const uniqueIds = new Set<string>();
models.forEach(m => uniqueIds.add(m.entryId.toUpperCase()));
const idString = SetUtils.toArray(uniqueIds).join('-');
return `${idString || 'molstar-model'}`;
}
exportObj() {
const task = Task.create('Export OBJ', async ctx => {
try {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const objExporter = new ObjExporter(filename);
for (let i = 0, il = renderObjects.length; i < il; ++i) {
await ctx.update({ message: `Exporting object ${i}/${il}` });
await objExporter.add(renderObjects[i], this.plugin.canvas3d?.webgl!, ctx);
}
const { obj, mtl } = objExporter.getData();
const asciiWrite = (data: Uint8Array, str: string) => {
for (let i = 0, il = str.length; i < il; ++i) {
data[i] = str.charCodeAt(i);
}
};
const objData = new Uint8Array(obj.length);
asciiWrite(objData, obj);
const mtlData = new Uint8Array(mtl.length);
asciiWrite(mtlData, mtl);
const zipDataObj = {
[filename + '.obj']: objData,
[filename + '.mtl']: mtlData
};
const zipData = await zip(ctx, zipDataObj);
return {
zipData,
filename: filename + '.zip'
};
} catch (e) {
this.plugin.log.error('' + e);
throw e;
}
});
return this.plugin.runTask(task, { useOverlay: true });
}
constructor(private plugin: PluginContext) {
super();
}
}

View File

@@ -0,0 +1,369 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { MeshValues } from '../../mol-gl/renderable/mesh';
import { LinesValues } from '../../mol-gl/renderable/lines';
import { PointsValues } from '../../mol-gl/renderable/points';
import { SpheresValues } from '../../mol-gl/renderable/spheres';
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
import { TextureMeshValues } from '../../mol-gl/renderable/texture-mesh';
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
import { TextureImage } from '../../mol-gl/renderable/util';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { StringBuilder } from '../../mol-util';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4 = Vec3.transformMat4;
const v3transformMat3 = Vec3.transformMat3;
const mat3directionTransform = Mat3.directionTransform;
type RenderObjectExportData = {
[k: string]: string | Uint8Array | undefined
}
interface RenderObjectExporter<D extends RenderObjectExportData> {
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
getData(): D
}
// http://paulbourke.net/dataformats/obj/
// http://paulbourke.net/dataformats/mtl/
export type ObjData = {
obj: string
mtl: string
}
export class ObjExporter implements RenderObjectExporter<ObjData> {
private obj = StringBuilder.create();
private mtl = StringBuilder.create();
private vertexOffset = 0;
private currentColor: Color | undefined;
private currentAlpha: number | undefined;
private materialSet = new Set<string>();
private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
const r = tSize.array[i * 3];
const g = tSize.array[i * 3 + 1];
const b = tSize.array[i * 3 + 2];
return decodeFloatRGB(r, g, b);
}
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
const tSize = values.tSize.ref.value;
let size = 0;
switch (values.dSizeType.ref.value) {
case 'uniform':
size = values.uSize.ref.value;
break;
case 'instance':
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex) / 100;
break;
case 'group':
size = ObjExporter.getSizeFromTexture(tSize, group) / 100;
break;
case 'groupInstance':
const groupCount = values.uGroupCount.ref.value;
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group) / 100;
break;
}
return size * values.uSizeFactor.ref.value;
}
private static getGroup(groups: Float32Array | Uint8Array, i: number): number {
const i4 = i * 4;
const r = groups[i4];
const g = groups[i4 + 1];
const b = groups[i4 + 2];
if (groups instanceof Float32Array) {
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
}
return decodeFloatRGB(r, g, b);
}
private updateMaterial(color: Color, alpha: number) {
if (this.currentColor === color && this.currentAlpha === alpha) return;
this.currentColor = color;
this.currentAlpha = alpha;
const material = Color.toHexString(color) + alpha;
StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
StringBuilder.newline(this.obj);
if (!this.materialSet.has(material)) {
this.materialSet.add(material);
const [r, g, b] = Color.toRgbNormalized(color);
const mtl = this.mtl;
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
StringBuilder.writeFloat(mtl, r, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, g, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, b, 1000);
StringBuilder.newline(mtl);
StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
StringBuilder.writeSafe(mtl, 'd '); // dissolve
StringBuilder.writeFloat(mtl, alpha, 1000);
StringBuilder.newline(mtl);
}
}
private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, geoTexture: boolean, ctx: RuntimeContext) {
const obj = this.obj;
const t = Mat4();
const n = Mat3();
const tmpV = Vec3();
const stride = geoTexture ? 4 : 3;
const colorType = values.dColorType.ref.value;
const tColor = values.tColor.ref.value.array;
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
Mat4.fromArray(t, aTransform, instanceIndex * 16);
mat3directionTransform(n, t);
const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
// position
for (let i = 0; i < vertexCount; ++i) {
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
StringBuilder.writeSafe(obj, 'v ');
StringBuilder.writeFloat(obj, tmpV[0], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 1000);
StringBuilder.newline(obj);
}
// normal
for (let i = 0; i < vertexCount; ++i) {
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
StringBuilder.writeSafe(obj, 'vn ');
StringBuilder.writeFloat(obj, tmpV[0], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 100);
StringBuilder.newline(obj);
}
// face
for (let i = 0; i < drawCount; i += 3) {
if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + i });
let color: Color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
const group = geoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
const groupCount = values.uGroupCount.ref.value;
const group = geoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
}
case 'vertex':
color = Color.fromArray(tColor, i * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * drawCount + i) * 3);
break;
default: throw new Error('Unsupported color type.');
}
this.updateMaterial(color, uAlpha);
const v1 = this.vertexOffset + (geoTexture ? i : indices![i]) + 1;
const v2 = this.vertexOffset + (geoTexture ? i + 1 : indices![i + 1]) + 1;
const v3 = this.vertexOffset + (geoTexture ? i + 2 : indices![i + 2]) + 1;
StringBuilder.writeSafe(obj, 'f ');
StringBuilder.writeInteger(obj, v1);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v1);
StringBuilder.writeInteger(obj, v2);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v2);
StringBuilder.writeInteger(obj, v3);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeInteger(obj, v3);
StringBuilder.newline(obj);
}
this.vertexOffset += vertexCount;
}
private async addMesh(values: MeshValues, ctx: RuntimeContext) {
const aPosition = values.aPosition.ref.value;
const aNormal = values.aNormal.ref.value;
const elements = values.elements.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
const drawCount = values.drawCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, false, ctx);
}
}
private async addLines(values: LinesValues, ctx: RuntimeContext) {
// TODO
}
private async addPoints(values: PointsValues, ctx: RuntimeContext) {
// TODO
}
private async addSpheres(values: SpheresValues, ctx: RuntimeContext) {
const center = Vec3();
const aPosition = values.aPosition.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 4) {
v3fromArray(center, aPosition, i * 3);
const group = aGroup[i];
const radius = ObjExporter.getSize(values, instanceIndex, group);
state.currentGroup = group;
addSphere(state, center, radius, 2);
}
const mesh = MeshBuilder.getMesh(state);
const vertices = mesh.vertexBuffer.ref.value;
const normals = mesh.normalBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const groups = mesh.groupBuffer.ref.value;
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, false, ctx);
}
}
private async addCylinders(values: CylindersValues, ctx: RuntimeContext) {
const start = Vec3();
const end = Vec3();
const aStart = values.aStart.ref.value;
const aEnd = values.aEnd.ref.value;
const aScale = values.aScale.ref.value;
const aCap = values.aCap.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 6) {
v3fromArray(start, aStart, i * 3);
v3fromArray(end, aEnd, i * 3);
const group = aGroup[i];
const radius = ObjExporter.getSize(values, instanceIndex, group) * aScale[i];
const cap = aCap[i];
const topCap = cap === 1 || cap === 3;
const bottomCap = cap >= 2;
const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments: 32 };
state.currentGroup = aGroup[i];
addCylinder(state, start, end, 1, cylinderProps);
}
const mesh = MeshBuilder.getMesh(state);
const vertices = mesh.vertexBuffer.ref.value;
const normals = mesh.normalBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const groups = mesh.groupBuffer.ref.value;
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, false, ctx);
}
}
private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
const GeoExportName = 'geo-export';
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[GeoExportName];
const [ width, height ] = values.uGeoTexDim.ref.value;
const vertices = new Float32Array(width * height * 4);
const normals = new Float32Array(width * height * 4);
const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4);
framebuffer.bind();
values.tPosition.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, vertices);
values.tNormal.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, normals);
values.tGroup.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, groups);
const vertexCount = values.uVertexCount.ref.value;
const instanceCount = values.instanceCount.ref.value;
const drawCount = values.drawCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
await this.addMeshWithColors(vertices, normals, undefined, groups, vertexCount, drawCount, values, instanceIndex, true, ctx);
}
}
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
if (!renderObject.state.visible) return;
switch (renderObject.type) {
case 'mesh':
return this.addMesh(renderObject.values as MeshValues, ctx);
case 'lines':
return this.addLines(renderObject.values as LinesValues, ctx);
case 'points':
return this.addPoints(renderObject.values as PointsValues, ctx);
case 'spheres':
return this.addSpheres(renderObject.values as SpheresValues, ctx);
case 'cylinders':
return this.addCylinders(renderObject.values as CylindersValues, ctx);
case 'texture-mesh':
return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
}
}
getData() {
return {
obj: StringBuilder.getString(this.obj),
mtl: StringBuilder.getString(this.mtl)
};
}
constructor(filename: string) {
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { GeometryExporterUI } from './ui';
export const GeometryExport = PluginBehavior.create<{ }>({
name: 'extension-geo-export',
category: 'misc',
display: {
name: 'Geometry Export'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.customStructureControls.set('geo-export', GeometryExporterUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('geo-export');
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button } from '../../mol-plugin-ui/controls/common';
import { GetAppSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
import { download } from '../../mol-util/download';
import { GeometryControls } from './controls';
interface State {
busy?: boolean
}
export class GeometryExporterUI extends CollapsableControls<{}, State> {
private _controls: GeometryControls | undefined;
get controls() {
return this._controls || (this._controls = new GeometryControls(this.plugin));
}
protected defaultState(): State & CollapsableState {
return {
header: 'Export Geometries',
isCollapsed: true,
brand: { accent: 'cyan', svg: CubeSendSvg }
};
}
protected renderControls(): JSX.Element {
return <>
<Button icon={GetAppSvg}
onClick={this.saveObj} style={{ marginTop: 1 }}
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
Save OBJ + MTL
</Button>
</>;
}
componentDidMount() {
this.subscribe(this.plugin.canvas3d!.reprCount, () => {
if (!this.state.isCollapsed) this.forceUpdate();
});
}
componentWillUnmount() {
this._controls?.dispose();
this._controls = void 0;
}
saveObj = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.exportObj();
this.setState({ busy: false });
download(new Blob([data.zipData]), data.filename);
} catch {
this.setState({ busy: false });
}
}
}

View File

@@ -87,7 +87,12 @@ class Camera implements ICamera {
if (changed) {
Mat4.mul(this.projectionView, this.projection, this.view);
Mat4.invert(this.inverseProjectionView, this.projectionView);
if (!Mat4.tryInvert(this.inverseProjectionView, this.projectionView)) {
Mat4.copy(this.view, this.prevView);
Mat4.copy(this.projection, this.prevProjection);
Mat4.mul(this.projectionView, this.projection, this.view);
return false;
}
Mat4.copy(this.prevView, this.view);
Mat4.copy(this.prevProjection, this.projection);
@@ -230,7 +235,7 @@ namespace Camera {
target: Vec3.create(0, 0, 0),
radius: 0,
radiusMax: 0,
radiusMax: 10,
fog: 50,
clipFar: true
};
@@ -267,6 +272,18 @@ namespace Camera {
return out;
}
export function areSnapshotsEqual(a: Snapshot, b: Snapshot) {
return a.mode === b.mode
&& a.fov === b.fov
&& a.radius === b.radius
&& a.radiusMax === b.radiusMax
&& a.fog === b.fog
&& a.clipFar === b.clipFar
&& Vec3.exactEquals(a.position, b.position)
&& Vec3.exactEquals(a.up, b.up)
&& Vec3.exactEquals(a.target, b.target);
}
}
function updateOrtho(camera: Camera) {

View File

@@ -39,6 +39,9 @@ class CameraTransitionManager {
this._target.radius = this._target.radiusMax;
}
if (this._target.radius < 0.01) this._target.radius = 0.01;
if (this._target.radiusMax < 0.01) this._target.radiusMax = 0.01;
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(this._target);
return;

View File

@@ -61,11 +61,17 @@ export const Canvas3DParams = {
}, { pivot: 'radius' }),
viewport: PD.MappedStatic('canvas', {
canvas: PD.Group({}),
custom: PD.Group({
'static-frame': PD.Group({
x: PD.Numeric(0),
y: PD.Numeric(0),
width: PD.Numeric(128),
height: PD.Numeric(128)
}),
'relative-frame': PD.Group({
x: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
y: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
width: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 }),
height: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 })
})
}),
@@ -100,7 +106,7 @@ interface Canvas3DContext {
}
namespace Canvas3DContext {
const DefaultAttribs = {
export const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
@@ -201,7 +207,7 @@ interface Canvas3D {
*/
commit(isSynchronous?: boolean): void
/**
* Funcion for external "animation" control
* Function for external "animation" control
* Calls commit.
*/
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
@@ -214,7 +220,11 @@ interface Canvas3D {
/** Reset the timers, used by "animate" */
resetTime(t: number): void
animate(): void
pause(): void
/**
* Pause animation loop and optionally any rendering
* @param noDraw pause any rendering
*/
pause(noDraw?: boolean): void
identify(x: number, y: number): PickData | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId | undefined): Representation.Loci
@@ -386,8 +396,10 @@ namespace Canvas3D {
let forceNextDraw = false;
let forceDrawAfterAllCommited = false;
let currentTime = 0;
let drawPaused = false;
function draw(force?: boolean) {
if (drawPaused) return;
if (render(!!force || forceNextDraw) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
@@ -429,11 +441,13 @@ namespace Canvas3D {
}
function animate() {
drawPaused = false;
controls.start(now());
if (animationFrameHandle === 0) _animate();
}
function pause() {
function pause(noDraw = false) {
drawPaused = noDraw;
cancelAnimationFrame(animationFrameHandle);
animationFrameHandle = 0;
}
@@ -805,12 +819,21 @@ namespace Canvas3D {
y = 0;
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
} else {
} else if (p.viewport.name === 'static-frame') {
x = p.viewport.params.x * webgl.pixelRatio;
y = p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
height = p.viewport.params.height * webgl.pixelRatio;
y = gl.drawingBufferHeight - height - p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
} else if (p.viewport.name === 'relative-frame') {
x = Math.round(p.viewport.params.x * gl.drawingBufferWidth);
height = Math.round(p.viewport.params.height * gl.drawingBufferHeight);
y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight);
width = Math.round(p.viewport.params.width * gl.drawingBufferWidth);
// if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x;
// if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1;
// console.log({ x, y, width, height });
}
}
function syncViewport() {

View File

@@ -128,8 +128,8 @@ export class PickHelper {
this.pickX = Math.ceil(x * this.pickScale);
this.pickY = Math.ceil(y * this.pickScale);
const pickWidth = Math.ceil(width * this.pickScale);
const pickHeight = Math.ceil(height * this.pickScale);
const pickWidth = Math.floor(width * this.pickScale);
const pickHeight = Math.floor(height * this.pickScale);
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
this.pickWidth = pickWidth;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 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>
@@ -13,7 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { DrawPass } from './draw';
@@ -70,6 +70,7 @@ const SsaoSchema = {
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
uTexSize: UniformSpec('v2'),
@@ -89,6 +90,7 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
@@ -118,6 +120,7 @@ const SsaoBlurSchema = {
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
};
@@ -139,6 +142,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
uNear: ValueCell.create(0.0),
uFar: ValueCell.create(10000.0),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
};
@@ -286,10 +290,14 @@ export class PostprocessingPass {
private readonly renderable: PostprocessingRenderable
private scale: number
private ssaoScale: number
private calcSsaoScale() {
// downscale ssao for high pixel-ratios
return Math.min(1, 1 / this.webgl.pixelRatio);
}
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
this.scale = 1 / this.webgl.pixelRatio;
this.ssaoScale = this.calcSsaoScale();
const { colorTarget, depthTexture } = drawPass;
const width = colorTarget.getWidth();
@@ -298,7 +306,7 @@ export class PostprocessingPass {
this.nSamples = 1;
this.blurKernelSize = 1;
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'nearest');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
@@ -317,14 +325,14 @@ export class PostprocessingPass {
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthBlurProxyTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
@@ -338,9 +346,13 @@ export class PostprocessingPass {
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const ssaoScale = this.calcSsaoScale();
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
this.ssaoScale = ssaoScale;
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.target.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.ssaoDepthTexture.define(sw, sh);
@@ -349,8 +361,8 @@ export class PostprocessingPass {
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
}
}
@@ -367,8 +379,22 @@ export class PostprocessingPass {
Mat4.invert(invProjection, camera.projection);
if (props.occlusion.name === 'on') {
ValueCell.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection);
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const b = this.ssaoRenderable.values.uBounds;
const v = camera.viewport;
const s = this.ssaoScale;
Vec4.set(b.ref.value,
Math.floor(v.x * s) / (w * s),
Math.floor(v.y * s) / (h * s),
Math.ceil((v.x + v.width) * s) / (w * s),
Math.ceil((v.y + v.height) * s) / (h * s)
);
ValueCell.update(b, b.ref.value);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
@@ -376,7 +402,9 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateSsaoBlur = true; }
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
needsUpdateSsaoBlur = true;
}
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
@@ -384,7 +412,7 @@ export class PostprocessingPass {
needsUpdateSsao = true;
this.nSamples = props.occlusion.params.samples;
ValueCell.updateIfChanged(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
}
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
@@ -394,10 +422,10 @@ export class PostprocessingPass {
needsUpdateSsaoBlur = true;
this.blurKernelSize = props.occlusion.params.blurKernelSize;
let kernel = getBlurKernel(this.blurKernelSize);
const kernel = getBlurKernel(this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
@@ -467,10 +495,10 @@ export class PostprocessingPass {
if (props.occlusion.name === 'on') {
const { x, y, width, height } = camera.viewport;
const sx = Math.floor(x * this.scale);
const sy = Math.floor(y * this.scale);
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const sx = Math.floor(x * this.ssaoScale);
const sy = Math.floor(y * this.ssaoScale);
const sw = Math.ceil(width * this.ssaoScale);
const sh = Math.ceil(height * this.ssaoScale);
this.webgl.gl.viewport(sx, sy, sw, sh);
this.webgl.gl.scissor(sx, sy, sw, sh);

View File

@@ -18,11 +18,24 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 've
export type ColorData = {
uColor: ValueCell<Vec3>,
tColor: ValueCell<TextureImage<Uint8Array>>,
tPalette: ValueCell<TextureImage<Uint8Array>>,
uColorTexDim: ValueCell<Vec2>,
dColorType: ValueCell<string>,
dUsePalette: ValueCell<boolean>,
}
export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
if (colorTheme.palette) {
ValueCell.updateIfChanged(data.dUsePalette, true);
updatePaletteTexture(colorTheme.palette, data.tPalette);
} else {
ValueCell.updateIfChanged(data.dUsePalette, false);
}
return data;
}
function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) {
case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData);
case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
@@ -42,18 +55,20 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
return {
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
dColorType: ValueCell.create('uniform'),
dUsePalette: ValueCell.create(false),
};
}
}
/** Creates color uniform */
export function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
return createValueColor(color(NullLocation, false), colorData);
}
export function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
if (colorData) {
ValueCell.update(colorData.tColor, colors);
ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height));
@@ -63,14 +78,16 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
return {
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create(colors),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
dColorType: ValueCell.create(type),
dUsePalette: ValueCell.create(false),
};
}
}
/** Creates color texture with color for each instance */
export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { instanceCount } = locationIt;
const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset();
@@ -83,7 +100,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio
}
/** Creates color texture with color for each group (i.e. shared across instances) */
export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount } = locationIt;
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset();
@@ -95,7 +112,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo
}
/** Creates color texture with color for each group in each instance */
export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, instanceCount } = locationIt;
const count = instanceCount * groupCount;
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
@@ -108,7 +125,7 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo
}
/** Creates color texture with color for each vertex (i.e. shared across instances) */
export function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, stride } = locationIt;
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset();
@@ -124,7 +141,7 @@ export function createVertexColor(locationIt: LocationIterator, color: LocationC
}
/** Creates color texture with color for each vertex in each instance */
export function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, instanceCount, stride } = locationIt;
const count = instanceCount * groupCount;
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
@@ -138,3 +155,34 @@ export function createVertexInstanceColor(locationIt: LocationIterator, color: L
}
return createTextureColor(colors, 'vertexInstance', colorData);
}
function updatePaletteTexture(palette: ColorTheme.Palette, cell: ValueCell<TextureImage<Uint8Array>>) {
let isSynced = true;
const texture = cell.ref.value;
if (palette.colors.length !== texture.width || texture.filter !== palette.filter) {
isSynced = false;
} else {
const data = texture.array;
let o = 0;
for (const c of palette.colors) {
const [r, g, b] = Color.toRgb(c);
if (data[o++] !== r || data[o++] !== g || data[o++] !== b) {
isSynced = false;
break;
}
}
}
if (isSynced) return;
const array = new Uint8Array(palette.colors.length * 3);
let o = 0;
for (const c of palette.colors) {
const [r, g, b] = Color.toRgb(c);
array[o++] = r;
array[o++] = g;
array[o++] = b;
}
ValueCell.update(cell, { array, height: 1, width: palette.colors.length, filter: palette.filter });
}

View File

@@ -55,7 +55,7 @@ export interface GeometryUtils<G extends Geometry, P extends PD.Params = Geometr
createValuesSimple(geometry: G, props: Partial<PD.Values<P>>, colorValue: Color, sizeValue: number, transform?: TransformData): V
updateValues(values: V, props: PD.Values<P>): void
updateBoundingSphere(values: V, geometry: G): void
createRenderableState(props: Partial<PD.Values<P>>): RenderableState
createRenderableState(props: PD.Values<P>): RenderableState
updateRenderableState(state: RenderableState, props: PD.Values<P>): void
createPositionIterator(geometry: G, transform: TransformData): LocationIterator
}

View File

@@ -144,6 +144,14 @@ export namespace MeshBuilder {
}
}
export function addMesh(state: State, t: Mat4, mesh: Mesh) {
addPrimitive(state, t, {
vertices: mesh.vertexBuffer.ref.value.subarray(0, mesh.vertexCount * 3),
normals: mesh.normalBuffer.ref.value.subarray(0, mesh.vertexCount * 3),
indices: mesh.indexBuffer.ref.value.subarray(0, mesh.triangleCount * 3),
});
}
export function getMesh (state: State): Mesh {
const { vertices, normals, indices, groups, mesh } = state;
const vb = ChunkedArray.compact(vertices, true) as Float32Array;

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
// adapted from three.js, MIT License Copyright 2010-2021 three.js authors
import { Vec3 } from '../../mol-math/linear-algebra';
import { Primitive } from './primitive';
export const DefaultTorusProps = {
radius: 1,
tube: 0.4,
radialSegments: 8,
tubularSegments: 6,
arc: Math.PI * 2,
};
export type TorusProps = Partial<typeof DefaultTorusProps>
export function Torus(props?: TorusProps): Primitive {
const { radius, tube, radialSegments, tubularSegments, arc } = { ...DefaultTorusProps, ...props };
// buffers
const indices: number[] = [];
const vertices: number[] = [];
const normals: number[] = [];
// helper variables
const center = Vec3();
const vertex = Vec3();
const normal = Vec3();
// generate vertices and normals
for (let j = 0; j <= radialSegments; ++j) {
for (let i = 0; i <= tubularSegments; ++i) {
const u = i / tubularSegments * arc;
const v = j / radialSegments * Math.PI * 2;
// vertex
Vec3.set(
vertex,
(radius + tube * Math.cos(v)) * Math.cos(u),
(radius + tube * Math.cos(v)) * Math.sin(u),
tube * Math.sin(v)
);
vertices.push(...vertex);
// normal
Vec3.set(center, radius * Math.cos(u), radius * Math.sin(u), 0 );
Vec3.sub(normal, vertex, center);
Vec3.normalize(normal, normal);
normals.push(...normal);
}
}
// generate indices
for (let j = 1; j <= radialSegments; ++j) {
for (let i = 1; i <= tubularSegments; ++i) {
// indices
const a = (tubularSegments + 1) * j + i - 1;
const b = (tubularSegments + 1) * (j - 1) + i - 1;
const c = (tubularSegments + 1) * (j - 1) + i;
const d = (tubularSegments + 1) * j + i;
// faces
indices.push(a, b, d);
indices.push(b, c, d);
}
}
return {
vertices: new Float32Array(vertices),
normals: new Float32Array(normals),
indices: new Uint32Array(indices)
};
}

View File

@@ -133,7 +133,7 @@ describe('renderer', () => {
scene.add(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(6);
expect(ctx.stats.resourceCounts.texture).toBe(7);
expect(ctx.stats.resourceCounts.vertexArray).toBe(6);
expect(ctx.stats.resourceCounts.program).toBe(6);
expect(ctx.stats.resourceCounts.shader).toBe(12);

View File

@@ -185,7 +185,9 @@ export const ColorSchema = {
uColor: UniformSpec('v3', 'material'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
dUsePalette: DefineSpec('boolean'),
} as const;
export type ColorSchema = typeof ColorSchema
export type ColorValues = Values<ColorSchema>

View File

@@ -7,6 +7,7 @@
import { Sphere3D } from '../../mol-math/geometry';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { TextureFilter } from '../webgl/texture';
export function calculateTextureInfo (n: number, itemSize: number) {
n = Math.max(n, 2); // observed issues with 1 pixel textures
@@ -22,6 +23,7 @@ export interface TextureImage<T extends Uint8Array | Float32Array | Int32Array>
readonly width: number
readonly height: number
readonly flipY?: boolean
readonly filter?: TextureFilter
}
export interface TextureVolume<T extends Uint8Array | Float32Array> {

View File

@@ -14,6 +14,10 @@ export const assign_color_varying = `
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#endif
#ifdef dUsePalette
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / 16777215.0;
#endif
#ifdef dOverpaint
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#endif

View File

@@ -1,6 +1,8 @@
export const assign_material_color = `
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
#if defined(dUsePalette)
vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha);
#elif defined(dColorType_uniform)
vec4 material = vec4(uColor, uAlpha);
#elif defined(dColorType_varying)
vec4 material = vec4(vColor.rgb, uAlpha);

View File

@@ -21,4 +21,9 @@ export const color_frag_params = `
varying float vGroup;
varying float vTransparency;
#endif
#ifdef dUsePalette
uniform sampler2D tPalette;
varying float vPaletteV;
#endif
`;

View File

@@ -30,4 +30,8 @@ export const color_vert_params = `
uniform vec2 uTransparencyTexDim;
uniform sampler2D tTransparency;
#endif
#ifdef dUsePalette
varying float vPaletteV;
#endif
`;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 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>
@@ -88,7 +88,8 @@ float getSsao(vec2 coords) {
} else if (rawSsao > 0.001) {
return rawSsao;
}
return 0.0;
// treat values close to 0.0 as errors and return no occlusion
return 1.0;
}
void main(void) {

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 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>
*/
export const ssaoBlur_frag = `
@@ -11,6 +12,7 @@ precision highp sampler2D;
uniform sampler2D tSsaoDepth;
uniform vec2 uTexSize;
uniform vec4 uBounds;
uniform float uKernel[dOcclusionKernelSize];
@@ -36,16 +38,25 @@ bool isBackground(const in float depth) {
return depth == 1.0;
}
bool outsideBounds(const in vec2 p) {
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
}
void main(void) {
vec2 coords = gl_FragCoord.xy / uTexSize;
vec2 packedDepth = texture2D(tSsaoDepth, coords).zw;
if (outsideBounds(coords)) {
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
return;
}
float selfDepth = unpackRGToUnitInterval(packedDepth);
// if background and if second pass
if (isBackground(selfDepth) && uBlurDirectionY != 0.0) {
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
return;
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
return;
}
float selfViewZ = getViewZ(selfDepth);
@@ -57,6 +68,9 @@ void main(void) {
// only if kernelSize is odd
for (int i = -dOcclusionKernelSize / 2; i <= dOcclusionKernelSize / 2; i++) {
vec2 sampleCoords = coords + float(i) * offset;
if (outsideBounds(sampleCoords)) {
continue;
}
vec4 sampleSsaoDepth = texture2D(tSsaoDepth, sampleCoords);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 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>
@@ -13,14 +13,14 @@ precision highp sampler2D;
#include common
uniform sampler2D tDepth;
uniform vec2 uTexSize;
uniform vec4 uBounds;
uniform vec3 uSamples[dNSamples];
uniform mat4 uProjection;
uniform mat4 uInvProjection;
uniform vec2 uTexSize;
uniform float uRadius;
uniform float uBias;
@@ -46,8 +46,12 @@ bool isBackground(const in float depth) {
return depth == 1.0;
}
bool outsideBounds(const in vec2 p) {
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
}
float getDepth(const in vec2 coords) {
return unpackRGBAToDepth(texture2D(tDepth, coords));
return outsideBounds(coords) ? 1.0 : unpackRGBAToDepth(texture2D(tDepth, coords));
}
vec3 normalFromDepth(const in float depth, const in float depth1, const in float depth2, vec2 offset1, vec2 offset2) {

View File

@@ -283,6 +283,9 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
} else if (isTexture2d(data, target, gl)) {
const _filter = data.filter ? getFilter(gl, data.filter) : filter;
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, _filter);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, _filter);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
if (sub) {
gl.texSubImage2D(target, 0, 0, 0, data.width, data.height, format, type, data.array);

View File

@@ -2,7 +2,7 @@
import { parseSdf } from '../sdf/parser';
const SdfString = `
Mrv1718007121815122D
Mrv1718007121815122D
5 4 0 0 0 0 999 V2000
0.0000 0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
@@ -129,7 +129,208 @@ Comp 2
M CHG 3 1 -1 3 -1 5 -1
M END
> <DATABASE_ID>
1`;
1
$$$$
2244
-OEChem-04122119123D
21 21 0 0 0 0 0 0 0999 V2000
1.2333 0.5540 0.7792 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6952 -2.7148 -0.7502 O 0 0 0 0 0 0 0 0 0 0 0 0
0.7958 -2.1843 0.8685 O 0 0 0 0 0 0 0 0 0 0 0 0
1.7813 0.8105 -1.4821 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.0857 0.6088 0.4403 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7927 -0.5515 0.1244 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7288 1.8464 0.4133 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.1426 -0.4741 -0.2184 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0787 1.9238 0.0706 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.7855 0.7636 -0.2453 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1409 -1.8536 0.1477 C 0 0 0 0 0 0 0 0 0 0 0 0
2.1094 0.6715 -0.3113 C 0 0 0 0 0 0 0 0 0 0 0 0
3.5305 0.5996 0.1635 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1851 2.7545 0.6593 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.7247 -1.3605 -0.4564 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.5797 2.8872 0.0506 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.8374 0.8238 -0.5090 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7290 1.4184 0.8593 H 0 0 0 0 0 0 0 0 0 0 0 0
4.2045 0.6969 -0.6924 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7105 -0.3659 0.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.2555 -3.5916 -0.7337 H 0 0 0 0 0 0 0 0 0 0 0 0
1 5 1 0 0 0 0
1 12 1 0 0 0 0
2 11 1 0 0 0 0
2 21 1 0 0 0 0
3 11 2 0 0 0 0
4 12 2 0 0 0 0
5 6 1 0 0 0 0
5 7 2 0 0 0 0
6 8 2 0 0 0 0
6 11 1 0 0 0 0
7 9 1 0 0 0 0
7 14 1 0 0 0 0
8 10 1 0 0 0 0
8 15 1 0 0 0 0
9 10 2 0 0 0 0
9 16 1 0 0 0 0
10 17 1 0 0 0 0
12 13 1 0 0 0 0
13 18 1 0 0 0 0
13 19 1 0 0 0 0
13 20 1 0 0 0 0
M END
> <PUBCHEM_COMPOUND_CID>
2244
> <PUBCHEM_CONFORMER_RMSD>
0.6
> <PUBCHEM_CONFORMER_DIVERSEORDER>
1
11
10
3
15
17
13
5
16
7
14
9
8
4
18
6
12
2
> <PUBCHEM_MMFF94_PARTIAL_CHARGES>
18
1 -0.23
10 -0.15
11 0.63
12 0.66
13 0.06
14 0.15
15 0.15
16 0.15
17 0.15
2 -0.65
21 0.5
3 -0.57
4 -0.57
5 0.08
6 0.09
7 -0.15
8 -0.15
9 -0.15
> <PUBCHEM_EFFECTIVE_ROTOR_COUNT>
3
> <PUBCHEM_PHARMACOPHORE_FEATURES>
5
1 2 acceptor
1 3 acceptor
1 4 acceptor
3 2 3 11 anion
6 5 6 7 8 9 10 rings
> <PUBCHEM_HEAVY_ATOM_COUNT>
13
> <PUBCHEM_ATOM_DEF_STEREO_COUNT>
0
> <PUBCHEM_ATOM_UDEF_STEREO_COUNT>
0
> <PUBCHEM_BOND_DEF_STEREO_COUNT>
0
> <PUBCHEM_BOND_UDEF_STEREO_COUNT>
0
> <PUBCHEM_ISOTOPIC_ATOM_COUNT>
0
> <PUBCHEM_COMPONENT_COUNT>
1
> <PUBCHEM_CACTVS_TAUTO_COUNT>
1
> <PUBCHEM_CONFORMER_ID>
000008C400000001
> <PUBCHEM_MMFF94_ENERGY>
39.5952
> <PUBCHEM_FEATURE_SELFOVERLAP>
25.432
> <PUBCHEM_SHAPE_FINGERPRINT>
1 1 18265615372930943622
100427 49 16967750034970055351
12138202 97 18271247217817981012
12423570 1 16692715976000295083
12524768 44 16753525617747228747
12716758 59 18341332292274886536
13024252 1 17968377969333732145
14181834 199 17830728755827362645
14614273 12 18262232214645093005
15207287 21 17703787037639964108
15775835 57 18340488876329928641
16945 1 18271533103414939405
193761 8 17907860604865584321
20645476 183 17677348215414174190
20871998 184 18198632231250704846
21040471 1 18411412921197846465
21501502 16 18123463883164380929
23402539 116 18271795865171824860
23419403 2 13539898140662769886
23552423 10 18048876295495619569
23559900 14 18272369794190581304
241688 4 16179044415907240795
257057 1 17478316999871287486
2748010 2 18339085878070479087
305870 269 18263645056784260212
528862 383 18117272558388284091
53812653 8 18410289211719108569
7364860 26 17910392788380644719
81228 2 18050568744116491203
> <PUBCHEM_SHAPE_MULTIPOLES>
244.06
3.86
2.45
0.89
1.95
1.58
0.15
-1.85
0.38
-0.61
-0.02
0.29
0.01
-0.33
> <PUBCHEM_SHAPE_SELFOVERLAP>
513.037
> <PUBCHEM_SHAPE_VOLUME>
136
> <PUBCHEM_COORDINATE_TYPE>
2
5
10
$$$$
`;
describe('sdf reader', () => {
it('basic', async () => {
@@ -139,10 +340,11 @@ describe('sdf reader', () => {
}
const compound1 = parsed.result.compounds[0];
const compound2 = parsed.result.compounds[1];
const compound3 = parsed.result.compounds[2];
const { molFile, dataItems } = compound1;
const { atoms, bonds } = molFile;
expect(parsed.result.compounds.length).toBe(2);
expect(parsed.result.compounds.length).toBe(3);
// number of structures
expect(atoms.count).toBe(5);
@@ -171,5 +373,11 @@ describe('sdf reader', () => {
expect(compound1.dataItems.data.value(0)).toBe('0');
expect(compound2.dataItems.data.value(0)).toBe('1');
expect(compound3.dataItems.dataHeader.value(2)).toBe('PUBCHEM_CONFORMER_DIVERSEORDER');
expect(compound3.dataItems.data.value(2)).toBe('1\n11\n10\n3\n15\n17\n13\n5\n16\n7\n14\n9\n8\n4\n18\n6\n12\n2');
expect(compound3.dataItems.dataHeader.value(21)).toBe('PUBCHEM_COORDINATE_TYPE');
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
});
});

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column } from '../../../mol-data/db';
@@ -27,21 +28,31 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
const data = TokenBuilder.create(tokenizer.data, 32);
let sawHeaderToken = false;
while (tokenizer.position < tokenizer.length) {
const line = Tokenizer.readLine(tokenizer);
if (line.startsWith(delimiter)) break;
if (!!line) {
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
sawHeaderToken = true;
} else if (sawHeaderToken) {
TokenBuilder.add(data, tokenizer.tokenStart, tokenizer.tokenEnd);
sawHeaderToken = false;
// TODO can there be multiline values?
if (!line) continue;
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
Tokenizer.markLine(tokenizer);
const start = tokenizer.tokenStart;
let end = tokenizer.tokenEnd;
let added = false;
while (tokenizer.position < tokenizer.length) {
const line2 = Tokenizer.readLine(tokenizer);
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> <')) {
TokenBuilder.add(data, start, end);
added = true;
break;
}
end = tokenizer.tokenEnd;
}
if (!added) {
TokenBuilder.add(data, start, end);
}
} else {
sawHeaderToken = false;
}
}

View File

@@ -333,7 +333,7 @@ namespace Mat4 {
return out;
}
export function invert(out: Mat4, a: Mat4) {
export function tryInvert(out: Mat4, a: Mat4) {
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
@@ -356,8 +356,7 @@ namespace Mat4 {
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (!det) {
console.warn('non-invertible matrix.', a);
return out;
return false;
}
det = 1.0 / det;
@@ -378,6 +377,13 @@ namespace Mat4 {
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
return true;
}
export function invert(out: Mat4, a: Mat4) {
if (!tryInvert(out, a)) {
console.warn('non-invertible matrix.', a);
}
return out;
}

View File

@@ -1167,6 +1167,57 @@ namespace Structure {
}
}
export interface ForEachAtomicHierarchyElementParams {
// Called for 1st element of each chain
// Note that chains can be split, meaning each chain would be called multiple times.
chain?: (e: StructureElement.Location<Unit.Atomic>) => void,
// Called for 1st element of each residue
residue?: (e: StructureElement.Location<Unit.Atomic>) => void,
// Called for each element
atom?: (e: StructureElement.Location<Unit.Atomic>) => void,
};
export function eachAtomicHierarchyElement(structure: Structure, { chain, residue, atom }: ForEachAtomicHierarchyElementParams) {
const l = StructureElement.Location.create<Unit.Atomic>(structure);
for (const unit of structure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
if (chain) {
l.element = elements[chainSegment.start];
chain(l);
}
if (!residue && !atom) continue;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
if (residue) {
l.element = elements[residueSegment.start];
residue(l);
}
if (!atom) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
l.element = elements[j];
atom(l);
}
}
}
}
}
//
export const DefaultSizeThresholds = {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -12,7 +12,7 @@ import { StatefulPluginComponent } from '../component';
import { PluginContext } from '../../mol-plugin/context';
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
import { Asset } from '../../mol-util/assets';
import { zip } from '../../mol-util/zip/zip';
import { Zip } from '../../mol-util/zip/zip';
import { readFromFile } from '../../mol-util/data-source';
import { objectForEach } from '../../mol-util/object';
import { PLUGIN_VERSION } from '../../mol-plugin/version';
@@ -217,7 +217,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
zipDataObj['assets.json'] = data;
}
const zipFile = zip(zipDataObj);
const zipFile = await this.plugin.runTask(Zip(zipDataObj));
return new Blob([zipFile], {type : 'application/zip'});
}
}

View File

@@ -10,7 +10,7 @@ import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
import { QueryContext, Structure, StructureElement, StructureQuery, StructureSelection } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { StateObjectRef } from '../../../mol-state';
import { Task } from '../../../mol-task';
@@ -457,6 +457,13 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
this.triggerInteraction(modifier, loci, applyGranularity);
}
fromCompiledQuery(modifier: StructureSelectionModifier, query: StructureQuery, applyGranularity = true) {
for (const s of this.applicableStructures) {
const loci = query(new QueryContext(s));
this.triggerInteraction(modifier, StructureSelection.toLociWithSourceUnits(loci), applyGranularity);
}
}
fromSelectionQuery(modifier: StructureSelectionModifier, query: StructureSelectionQuery, applyGranularity = true) {
this.plugin.runTask(Task.create('Structure Selection', async runtime => {
for (const s of this.applicableStructures) {

View File

@@ -17,13 +17,14 @@ export function buildVolumeHierarchy(state: State, previous?: VolumeHierarchy) {
export interface VolumeHierarchy {
volumes: VolumeRef[],
lazyVolumes: LazyVolumeRef[],
refs: Map<StateTransform.Ref, VolumeHierarchyRef>
// TODO: might be needed in the future
// decorators: Map<StateTransform.Ref, StateTransform>,
}
export function VolumeHierarchy(): VolumeHierarchy {
return { volumes: [], refs: new Map() };
return { volumes: [], lazyVolumes: [], refs: new Map() };
}
interface RefBase<K extends string = string, O extends StateObject = StateObject, T extends StateTransformer = StateTransformer> {
@@ -32,7 +33,7 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
version: StateTransform['version']
}
export type VolumeHierarchyRef = VolumeRef | VolumeRepresentationRef
export type VolumeHierarchyRef = VolumeRef | LazyVolumeRef | VolumeRepresentationRef
export interface VolumeRef extends RefBase<'volume', SO.Volume.Data> {
representations: VolumeRepresentationRef[]
@@ -42,6 +43,13 @@ function VolumeRef(cell: StateObjectCell<SO.Volume.Data>): VolumeRef {
return { kind: 'volume', cell, version: cell.transform.version, representations: [] };
}
export interface LazyVolumeRef extends RefBase<'lazy-volume', SO.Volume.Lazy> {
}
function LazyVolumeRef(cell: StateObjectCell<SO.Volume.Lazy>): LazyVolumeRef {
return { kind: 'lazy-volume', cell, version: cell.transform.version };
}
export interface VolumeRepresentationRef extends RefBase<'volume-representation', SO.Volume.Representation3D, StateTransforms['Representation']['VolumeRepresentation3D']> {
volume: VolumeRef
}
@@ -95,6 +103,10 @@ const Mapping: [TestCell, ApplyRef, LeaveRef][] = [
state.currentVolume = createOrUpdateRefList(state, cell, state.hierarchy.volumes, VolumeRef, cell);
}, state => state.currentVolume = void 0],
[cell => SO.Volume.Lazy.is(cell.obj), (state, cell) => {
createOrUpdateRefList(state, cell, state.hierarchy.lazyVolumes, LazyVolumeRef, cell);
}, noop],
[(cell, state) => {
return !cell.state.isGhost && !!state.currentVolume && SO.Volume.Representation3D.is(cell.obj);
}, (state, cell) => {

View File

@@ -22,6 +22,8 @@ import { VolumeRepresentation } from '../mol-repr/volume/representation';
import { StateObject, StateTransformer } from '../mol-state';
import { CubeFile } from '../mol-io/reader/cube/parser';
import { DxFile } from '../mol-io/reader/dx/parser';
import { Color } from '../mol-util/color/color';
import { Asset } from '../mol-util/assets';
export type TypeClass = 'root' | 'data' | 'prop'
@@ -119,7 +121,21 @@ export namespace PluginStateObject {
}
export namespace Volume {
export interface LazyInfo {
url: string | Asset.Url,
isBinary: boolean,
format: string,
entryId?: string,
isovalues: {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number
}[]
}
export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
export class Lazy extends Create<LazyInfo>({ name: 'Lazy Volume', typeClass: 'Object' }) { }
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>, _Volume>({ name: 'Volume 3D' }) { }
}

View File

@@ -19,6 +19,7 @@ import { PluginStateObject as SO, PluginStateTransform } from '../objects';
import { Asset } from '../../mol-util/assets';
import { parseCube } from '../../mol-io/reader/cube/parser';
import { parseDx } from '../../mol-io/reader/dx/parser';
import { ColorNames } from '../../mol-util/color/names';
export { Download };
export { DownloadBlob };
@@ -35,6 +36,7 @@ export { ParseDx };
export { ImportString };
export { ImportJson };
export { ParseJson };
export { LazyVolume };
type Download = typeof Download
const Download = PluginStateTransform.BuiltIn({
@@ -441,4 +443,31 @@ const ParseJson = PluginStateTransform.BuiltIn({
return new SO.Format.Json(json);
});
}
});
});
type LazyVolume = typeof LazyVolume
const LazyVolume = PluginStateTransform.BuiltIn({
name: 'lazy-volume',
display: { name: 'Lazy Volume', description: 'A placeholder for lazy loaded volume representation' },
from: SO.Root,
to: SO.Volume.Lazy,
params: {
url: PD.Url(''),
isBinary: PD.Boolean(false),
format: PD.Text('ccp4'), // TODO: use Select based on available formats
entryId: PD.Text(''),
isovalues: PD.ObjectList({
type: PD.Text<'absolute' | 'relative'>('relative'), // TODO: Select
value: PD.Numeric(0),
color: PD.Color(ColorNames.black),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 })
}, e => `${e.type} ${e.value}`)
}
})({
apply({ a, params }) {
return Task.create('Lazy Volume', async ctx => {
return new SO.Volume.Lazy(params, { label: `${params.entryId || params.url}`, description: 'Lazy Volume' });
});
}
});

View File

@@ -41,6 +41,9 @@ export function MoleculeSvg() { return _Molecule; }
const _CubeOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></svg>;
export function CubeOutlineSvg() { return _CubeOutline; }
const _CubeSend = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M16,4L9,8.04V15.96L16,20L23,15.96V8.04M16,6.31L19.8,8.5L16,10.69L12.21,8.5M0,7V9H7V7M11,10.11L15,12.42V17.11L11,14.81M21,10.11V14.81L17,17.11V12.42M2,11V13H7V11M4,15V17H7V15" /></svg>;
export function CubeSendSvg() { return _CubeSend; }
const _CursorDefaultOutline = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z' /></svg>;
export function CursorDefaultOutlineSvg() { return _CursorDefaultOutline; }

View File

@@ -10,14 +10,17 @@ import { Loci } from '../../mol-model/loci';
import { StructureElement } from '../../mol-model/structure';
import { StructureMeasurementCell, StructureMeasurementOptions, StructureMeasurementParams } from '../../mol-plugin-state/manager/structure/measurement';
import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginCommands } from '../../mol-plugin/commands';
import { AngleData } from '../../mol-repr/shape/loci/angle';
import { DihedralData } from '../../mol-repr/shape/loci/dihedral';
import { DistanceData } from '../../mol-repr/shape/loci/distance';
import { LabelData } from '../../mol-repr/shape/loci/label';
import { angleLabel, dihedralLabel, distanceLabel, lociLabel } from '../../mol-theme/label';
import { FiniteArray } from '../../mol-util/type-helpers';
import { CollapsableControls, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ExpandGroup, IconButton, ToggleButton } from '../controls/common';
import { Icon, PencilRulerSvg, SetSvg, ArrowUpwardSvg, ArrowDownwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, AddSvg, TuneSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, MoreHorizSvg } from '../controls/icons';
import { AddSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, PencilRulerSvg, SetSvg, TuneSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg } from '../controls/icons';
import { ParameterControls } from '../controls/parameters';
import { UpdateTransformControl } from '../state/update-transform';
import { ToggleSelectionModeButton } from './selection';
@@ -216,7 +219,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
}
get selections() {
return this.props.cell.obj?.data.sourceData as ReadonlyArray<PluginStateObject.Molecule.Structure.SelectionEntry> | undefined;
return this.props.cell.obj?.data.sourceData as Partial<DistanceData & AngleData & DihedralData & LabelData> | undefined;
}
delete = () => {
@@ -234,8 +237,8 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
if (!selections) return;
this.plugin.managers.interactivity.lociHighlights.clearHighlights();
for (const d of selections) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: d.loci }, false);
for (const loci of this.lociArray) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
}
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
}
@@ -250,21 +253,31 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
const selections = this.selections;
if (!selections) return;
const sphere = Loci.getBundleBoundingSphere(toLociBundle(selections));
const sphere = Loci.getBundleBoundingSphere({ loci: this.lociArray });
if (sphere) {
this.plugin.managers.camera.focusSphere(sphere);
}
}
private get lociArray(): FiniteArray<Loci> {
const selections = this.selections;
if (!selections) return [];
if (selections.infos) return [selections.infos[0].loci];
if (selections.pairs) return selections.pairs[0].loci;
if (selections.triples) return selections.triples[0].loci;
if (selections.quads) return selections.quads[0].loci;
return [];
}
get label() {
const selections = this.selections;
switch (selections?.length) {
case 1: return lociLabel(selections[0].loci, { condensed: true });
case 2: return distanceLabel(toLociBundle(selections), { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
case 3: return angleLabel(toLociBundle(selections), { condensed: true });
case 4: return dihedralLabel(toLociBundle(selections), { condensed: true });
default: return '';
}
if (!selections) return '<empty>';
if (selections.infos) return lociLabel(selections.infos[0].loci, { condensed: true });
if (selections.pairs) return distanceLabel(selections.pairs[0], { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
if (selections.triples) return angleLabel(selections.triples[0], { condensed: true });
if (selections.quads) return dihedralLabel(selections.quads[0], { condensed: true });
return '<empty>';
}
get actions(): ActionMenu.Items {
@@ -302,8 +315,4 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
</>}
</>;
}
}
function toLociBundle(data: FiniteArray<{ loci: Loci }, any>): { loci: FiniteArray<Loci, any> } {
return { loci: (data.map(d => d.loci) as unknown as FiniteArray<Loci, any>) };
}

View File

@@ -6,22 +6,24 @@
*/
import * as React from 'react';
import { StructureSelectionQueries, StructureSelectionQuery, getNonStandardResidueQueries, getElementQueries, getPolymerAndBranchedEntityQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
import { Structure } from '../../mol-model/structure/structure/structure';
import { getElementQueries, getNonStandardResidueQueries, getPolymerAndBranchedEntityQueries, StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
import { StructureRef, StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureComponentRef, StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureSelectionModifier } from '../../mol-plugin-state/manager/structure/selection';
import { PluginContext } from '../../mol-plugin/context';
import { compileResidueListSelection } from '../../mol-script/util/residue-list';
import { memoizeLatest } from '../../mol-util/memoize';
import { ParamDefinition } from '../../mol-util/param-definition';
import { stripTags } from '../../mol-util/string';
import { capitalize, stripTags } from '../../mol-util/string';
import { PluginUIComponent, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/common';
import { BrushSvg, CancelOutlinedSvg, CloseSvg, CubeOutlineSvg, HelpOutlineSvg, Icon, IntersectSvg, RemoveSvg, RestoreSvg, SelectionModeSvg, SetSvg, SubtractSvg, UnionSvg } from '../controls/icons';
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
import { UnionSvg, SubtractSvg, IntersectSvg, SetSvg, CubeOutlineSvg, Icon, SelectionModeSvg, RemoveSvg, RestoreSvg, HelpOutlineSvg, CancelOutlinedSvg, BrushSvg, CloseSvg } from '../controls/icons';
import { HelpGroup, HelpText, ViewportHelpContent } from '../viewport/help';
import { AddComponentControls } from './components';
import { Structure } from '../../mol-model/structure/structure/structure';
import { ViewportHelpContent, HelpGroup, HelpText } from '../viewport/help';
export class ToggleSelectionModeButton extends PurePluginUIComponent<{ inline?: boolean }> {
@@ -47,12 +49,15 @@ const StructureSelectionParams = {
granularity: InteractivityManager.Params.granularity,
};
type SelectionHelperType = 'residue-list'
interface StructureSelectionActionsControlsState {
isEmpty: boolean,
isBusy: boolean,
canUndo: boolean,
action?: StructureSelectionModifier | 'theme' | 'add-component' | 'help'
action?: StructureSelectionModifier | 'theme' | 'add-component' | 'help',
helper?: SelectionHelperType,
}
const ActionHeader = new Map<StructureSelectionModifier, string>([
@@ -65,6 +70,7 @@ const ActionHeader = new Map<StructureSelectionModifier, string>([
export class StructureSelectionActionsControls extends PluginUIComponent<{}, StructureSelectionActionsControlsState> {
state = {
action: void 0 as StructureSelectionActionsControlsState['action'],
helper: void 0 as StructureSelectionActionsControlsState['helper'],
isEmpty: true,
isBusy: false,
@@ -118,7 +124,16 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
}
}
get structures () {
selectHelper: ActionMenu.OnSelect = (item, e) => {
console.log(item);
if (!item || !this.state.action) {
this.setState({ action: void 0, helper: void 0 });
return;
}
this.setState({ helper: (item.value as { kind: SelectionHelperType }).kind });
}
get structures() {
const structures: Structure[] = [];
for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
const structure = s.cell.obj?.data;
@@ -129,7 +144,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
private queriesItems: ActionMenu.Items[] = []
private queriesVersion = -1
get queries () {
get queries() {
const { registry } = this.plugin.query.structure;
if (registry.version !== this.queriesVersion) {
const structures = this.structures;
@@ -150,8 +165,25 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
return this.queriesItems;
}
private helpersItems?: ActionMenu.Items[] = void 0;
get helpers() {
if (this.helpersItems) return this.helpersItems;
// TODO: this is an initial implementation of the helper UI
// the plan is to add support to input queries in different languages
// after this has been implemented in mol-script
const helpers = [
{ kind: 'residue-list' as SelectionHelperType, category: 'Helpers', label: 'Residue List', description: 'Create a selection from a list of residue ranges.' }
];
this.helpersItems = ActionMenu.createItems(helpers, {
label: q => q.label,
category: q => q.category,
description: q => q.description
});
return this.helpersItems;
}
private showAction(q: StructureSelectionActionsControlsState['action']) {
return () => this.setState({ action: this.state.action === q ? void 0 : q });
return () => this.setState({ action: this.state.action === q ? void 0 : q, helper: void 0 });
}
toggleAdd = this.showAction('add')
@@ -187,6 +219,45 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
? `Undo ${this.plugin.state.data.latestUndoLabel}`
: 'Some mistakes of the past can be undone.';
let children: React.ReactNode | undefined = void 0;
if (this.state.action && !this.state.helper) {
children = <>
{(this.state.action && this.state.action !== 'theme' && this.state.action !== 'add-component' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'>
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
<ActionMenu items={this.helpers} onSelect={this.selectHelper} noOffset />
</div>}
{this.state.action === 'theme' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Theme' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleTheme} topRightIcon={CloseSvg}>
<ApplyThemeControls onApply={this.toggleTheme} />
</ControlGroup>
</div>}
{this.state.action === 'add-component' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Add Component' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddComponent} topRightIcon={CloseSvg}>
<AddComponentControls onApply={this.toggleAddComponent} forSelection />
</ControlGroup>
</div>}
{this.state.action === 'help' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Help' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelp} topRightIcon={CloseSvg} maxHeight='300px'>
<HelpGroup header='Selection Operations'>
<HelpText>Use <Icon svg={UnionSvg} inline /> <Icon svg={SubtractSvg} inline /> <Icon svg={IntersectSvg} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
</HelpGroup>
<HelpGroup header='Representation Operations'>
<HelpText>Use <Icon svg={BrushSvg} inline /> <Icon svg={CubeOutlineSvg} inline /> <Icon svg={RemoveSvg} inline /> <Icon svg={RestoreSvg} inline /> to color, create components, remove from components, or undo actions.</HelpText>
</HelpGroup>
<ViewportHelpContent selectOnly={true} />
</ControlGroup>
</div>}
</>;
} else if (ActionHeader.has(this.state.action as any) && this.state.helper === 'residue-list') {
const close = () => this.setState({ action: void 0, helper: void 0 });
children = <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Residue List' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={close} topRightIcon={CloseSvg}>
<ResidueListSelectionHelper modifier={this.state.action as any} plugin={this.plugin} close={close} />
</ControlGroup>
</div>;
}
return <>
<div className='msp-flex-row' style={{ background: 'none' }}>
<PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
@@ -195,7 +266,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
<ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
<ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
<ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
<ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
<ToggleButton icon={CubeOutlineSvg} title='Create Component of Selection with Representation' toggle={this.toggleAddComponent} isSelected={this.state.action === 'add-component'} disabled={this.isDisabled} />
<IconButton svg={RemoveSvg} title='Remove/subtract Selection from all Components' onClick={this.subtract} disabled={this.isDisabled} />
<IconButton svg={RestoreSvg} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
@@ -203,30 +274,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
<ToggleButton icon={HelpOutlineSvg} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} />
<IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} />
</div>
{(this.state.action && this.state.action !== 'theme' && this.state.action !== 'add-component' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'>
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
</div>}
{this.state.action === 'theme' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Theme' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleTheme} topRightIcon={CloseSvg}>
<ApplyThemeControls onApply={this.toggleTheme} />
</ControlGroup>
</div>}
{this.state.action === 'add-component' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Add Component' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddComponent} topRightIcon={CloseSvg}>
<AddComponentControls onApply={this.toggleAddComponent} forSelection />
</ControlGroup>
</div>}
{this.state.action === 'help' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Help' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelp} topRightIcon={CloseSvg} maxHeight='300px'>
<HelpGroup header='Selection Operations'>
<HelpText>Use <Icon svg={UnionSvg} inline /> <Icon svg={SubtractSvg} inline /> <Icon svg={IntersectSvg} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
</HelpGroup>
<HelpGroup header='Representation Operations'>
<HelpText>Use <Icon svg={BrushSvg} inline /> <Icon svg={CubeOutlineSvg} inline /> <Icon svg={RemoveSvg} inline /> <Icon svg={RestoreSvg} inline /> to color, create components, remove from components, or undo actions.</HelpText>
</HelpGroup>
<ViewportHelpContent selectOnly={true} />
</ControlGroup>
</div>}
{children}
</>;
}
}
@@ -333,4 +381,34 @@ class ApplyThemeControls extends PurePluginUIComponent<ApplyThemeControlsProps,
</Button>
</>;
}
}
const ResidueListIdTypeParams = {
idType: ParamDefinition.Select<'auth' | 'label'>('auth', ParamDefinition.arrayToOptions(['auth', 'label'])),
residues: ParamDefinition.Text('', { description: 'A comma separated list of residue ranges in given chain, e.g. A 10-15, B 25, C 30:i' })
};
const DefaultResidueListIdTypeParams = ParamDefinition.getDefaultValues(ResidueListIdTypeParams);
function ResidueListSelectionHelper({ modifier, plugin, close }: { modifier: StructureSelectionModifier, plugin: PluginContext, close: () => void }) {
const [state, setState] = React.useState(DefaultResidueListIdTypeParams);
const apply = () => {
if (state.residues.length === 0) return;
try {
close();
const query = compileResidueListSelection(state.residues, state.idType);
plugin.managers.structure.selection.fromCompiledQuery(modifier, query, false);
} catch (e) {
plugin.log.error(`Failed to create selection: ${e}`);
}
};
return <>
<ParameterControls params={ResidueListIdTypeParams} values={state} onChangeValues={setState} onEnter={apply} />
<Button className='msp-btn-commit msp-btn-commit-on' disabled={state.residues.length === 0} onClick={apply} style={{ marginTop: '1px' }}>
{capitalize(modifier)} Selection
</Button>
</>;
}

View File

@@ -8,11 +8,11 @@
import * as React from 'react';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
import { VolumeHierarchyManager } from '../../mol-plugin-state/manager/volume/hierarchy';
import { VolumeRef, VolumeRepresentationRef } from '../../mol-plugin-state/manager/volume/hierarchy-state';
import { LazyVolumeRef, VolumeRef, VolumeRepresentationRef } from '../../mol-plugin-state/manager/volume/hierarchy-state';
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
import { InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { State, StateSelection, StateTransform } from '../../mol-state';
import { State, StateObjectCell, StateObjectSelector, StateSelection, StateTransform } from '../../mol-state';
import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ExpandGroup, IconButton } from '../controls/common';
@@ -21,6 +21,9 @@ import { UpdateTransformControl } from '../state/update-transform';
import { BindingsHelp } from '../viewport/help';
import { PluginCommands } from '../../mol-plugin/commands';
import { BlurOnSvg, ErrorSvg, CheckSvg, AddSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, DeleteOutlinedSvg, MoreHorizSvg } from '../controls/icons';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
interface VolumeStreamingControlState extends CollapsableState {
isBusy: boolean
@@ -104,6 +107,7 @@ export class VolumeStreamingControls extends CollapsableControls<{}, VolumeStrea
interface VolumeSourceControlState extends CollapsableState {
isBusy: boolean,
loadingLabel?: string,
show?: 'hierarchy' | 'add-repr'
}
@@ -120,18 +124,23 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
componentDidMount() {
this.subscribe(this.plugin.managers.volume.hierarchy.behaviors.selection, sel => {
this.setState({ isHidden: sel.hierarchy.volumes.length === 0 });
this.setState({ isHidden: sel.hierarchy.volumes.length === 0 && sel.hierarchy.lazyVolumes.length === 0 });
});
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
this.setState({ isBusy: v });
});
}
private item = (ref: VolumeRef) => {
private item = (ref: VolumeRef | LazyVolumeRef) => {
const selected = this.plugin.managers.volume.hierarchy.selection;
const label = ref.cell.obj?.label || 'Volume';
const item: ActionMenu.Item = { kind: 'item', label: label || ref.kind, selected: selected === ref, value: ref };
const item: ActionMenu.Item = {
kind: 'item',
label: (ref.kind === 'lazy-volume' ? 'Load ' : '') + (label || ref.kind),
selected: selected === ref,
value: ref
};
return item;
}
@@ -139,9 +148,15 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
const mng = this.plugin.managers.volume.hierarchy;
const { current } = mng;
const ret: ActionMenu.Items = [];
for (let ref of current.volumes) {
for (const ref of current.volumes) {
ret.push(this.item(ref));
}
for (const ref of current.lazyVolumes) {
ret.push(this.item(ref));
}
return ret;
}
@@ -158,11 +173,13 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
}
get isEmpty() {
const { volumes } = this.plugin.managers.volume.hierarchy.current;
return volumes.length === 0;
const { volumes, lazyVolumes } = this.plugin.managers.volume.hierarchy.current;
return volumes.length === 0 && lazyVolumes.length === 0;
}
get label() {
if (this.state.loadingLabel) return `Loading ${this.state.loadingLabel}...`;
const selected = this.plugin.managers.volume.hierarchy.selection;
if (!selected) return 'Nothing Selected';
return selected?.cell.obj?.label || 'Volume';
@@ -171,7 +188,45 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
selectCurrent: ActionMenu.OnSelect = (item) => {
this.toggleHierarchy();
if (!item) return;
this.plugin.managers.volume.hierarchy.setCurrent(item.value as VolumeRef);
const current = item.value as VolumeRef | LazyVolumeRef;
if (current.kind === 'volume') {
this.plugin.managers.volume.hierarchy.setCurrent(current);
} else {
this.lazyLoad(current.cell);
}
}
private async lazyLoad(cell: StateObjectCell<PluginStateObject.Volume.Lazy>) {
const { url, isBinary, format, entryId, isovalues } = cell.obj!.data;
this.setState({ isBusy: true, loadingLabel: cell.obj!.label });
try {
const plugin = this.plugin;
await plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build().to(volume);
for (const iso of isovalues) {
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
await plugin.build().delete(cell).commit();
});
} finally {
this.setState({ isBusy: false, loadingLabel: void 0 });
}
}
selectAdd: ActionMenu.OnSelect = (item) => {
@@ -186,13 +241,12 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
renderControls() {
const disabled = this.state.isBusy || this.isEmpty;
const label = this.label;
const selected = this.plugin.managers.volume.hierarchy.selection;
return <>
<div className='msp-flex-row' style={{ marginTop: '1px' }}>
<Button noOverflow flex onClick={this.toggleHierarchy} disabled={disabled} title={label}>{label}</Button>
{!this.isEmpty && <IconButton svg={AddSvg} onClick={this.toggleAddRepr} title='Apply a structure presets to the current hierarchy.' toggleState={this.state.show === 'add-repr'} disabled={disabled} />}
{!this.isEmpty && selected && <IconButton svg={AddSvg} onClick={this.toggleAddRepr} title='Apply a structure presets to the current hierarchy.' toggleState={this.state.show === 'add-repr'} disabled={disabled} />}
</div>
{this.state.show === 'hierarchy' && <ActionMenu items={this.hierarchyItems} onSelect={this.selectCurrent} />}
{this.state.show === 'add-repr' && <ActionMenu items={this.addActions} onSelect={this.selectAdd} />}

View File

@@ -34,12 +34,16 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
}
private copy = async () => {
await this.plugin.helpers.viewportScreenshot?.copyToClipboard();
PluginCommands.Toast.Show(this.plugin, {
message: 'Copied to clipboard.',
title: 'Screenshot',
timeoutMs: 1500
});
try {
await this.plugin.helpers.viewportScreenshot?.copyToClipboard();
PluginCommands.Toast.Show(this.plugin, {
message: 'Copied to clipboard.',
title: 'Screenshot',
timeoutMs: 1500
});
} catch {
return this.copyImg();
}
}
private copyImg = async () => {
@@ -71,8 +75,7 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
<CropControls plugin={this.plugin} />
</div>}
<div className='msp-flex-row'>
{hasClipboardApi && <Button icon={CopySvg} onClick={this.copy} disabled={this.state.isDisabled}>Copy</Button>}
{!hasClipboardApi && !this.state.imageData && <Button icon={CopySvg} onClick={this.copyImg} disabled={this.state.isDisabled}>Copy</Button>}
{!this.state.imageData && <Button icon={CopySvg} onClick={hasClipboardApi ? this.copy : this.copyImg} disabled={this.state.isDisabled}>Copy</Button>}
{this.state.imageData && <Button onClick={() => this.setState({ imageData: void 0 })} disabled={this.state.isDisabled}>Clear</Button>}
<Button icon={GetAppSvg} onClick={this.download} disabled={this.state.isDisabled}>Download</Button>
</div>

View File

@@ -184,17 +184,21 @@ export class PluginContext {
*/
readonly customState: unknown = Object.create(null);
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement, canvas3dContext?: Canvas3DContext) {
try {
this.layout.setRoot(container);
if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
const antialias = !(this.config.get(PluginConfig.General.DisableAntialiasing) ?? false);
const preserveDrawingBuffer = !(this.config.get(PluginConfig.General.DisablePreserveDrawingBuffer) ?? false);
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
if (canvas3dContext) {
(this.canvas3dContext as Canvas3DContext) = canvas3dContext;
} else {
const antialias = !(this.config.get(PluginConfig.General.DisableAntialiasing) ?? false);
const preserveDrawingBuffer = !(this.config.get(PluginConfig.General.DisablePreserveDrawingBuffer) ?? false);
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
}
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
this.canvas3dInit.next(true);
let props = this.spec.canvas3d;

View File

@@ -324,7 +324,7 @@ class ViewportScreenshotHelper extends PluginComponent {
await ctx.update('Converting image...');
const blob = await canvasToBlob(this.canvas, 'png');
const item = new ClipboardItem({ 'image/png': blob });
cb.write([item]);
await cb.write([item]);
this.plugin.log.message('Image copied to clipboard.');
});
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { StructureQuery } from '../../mol-model/structure/query';
import { Expression } from '../language/expression';
import { MolScriptBuilder as MS } from '../language/builder';
import { compile } from '../runtime/query/base';
// TODO: make this into a separate "language"?
type ResidueListSelectionEntry =
| { kind: 'single', asym_id: string; seq_id: number; ins_code?: string }
| { kind: 'range', asym_id: string; seq_id_beg: number; seq_id_end: number; }
function entriesToQuery(xs: ResidueListSelectionEntry[], kind: 'auth' | 'label') {
const groups: Expression[] = [];
const asym_id_key = kind === 'auth' ? 'auth_asym_id' as const : 'label_asym_id' as const;
const seq_id_key = kind === 'auth' ? 'auth_seq_id' as const : 'label_seq_id' as const;
for (const x of xs) {
if (x.kind === 'range') {
groups.push(MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.ammp(asym_id_key), x.asym_id]),
'residue-test': MS.core.rel.inRange([MS.ammp(seq_id_key), x.seq_id_beg, x.seq_id_end])
}));
} else {
const ins_code = (x.ins_code ?? '').trim();
groups.push(MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.ammp(asym_id_key), x.asym_id]),
'residue-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp(seq_id_key), x.seq_id]),
MS.core.rel.eq([MS.ammp('pdbx_PDB_ins_code'), ins_code])
])
}));
}
}
const query = MS.struct.combinator.merge(groups);
return compile(query) as StructureQuery;
}
function parseRange(c: string, s: string[], e: number): ResidueListSelectionEntry | undefined {
if (!c || s.length === 0 || Number.isNaN(+s[0])) return;
if (Number.isNaN(e)) {
return { kind: 'single', asym_id: c, seq_id: +s[0], ins_code: s[1] };
}
return { kind: 'range', asym_id: c, seq_id_beg: +s[0], seq_id_end: e };
}
function parseInsCode(e?: string) {
if (!e) return [];
return e.split(':');
}
function parseResidueListSelection(input: string): ResidueListSelectionEntry[] {
return input.split(',') // A 1-3, B 3 => [A 1-3, B 3]
.map(e => e.trim().split(/\s+|[-]/g).filter(e => !!e)) // [A 1-3, B 3] => [[A, 1, 3], [B, 3]]
.map(e => parseRange(e[0], parseInsCode(e[1]), +e[2]))
.filter(e => !!e) as ResidueListSelectionEntry[];
}
// parses a list of residue ranges, e.g. A 10-100, B 30, C 12:i
export function compileResidueListSelection(input: string, idType: 'auth' | 'label') {
const entries = parseResidueListSelection(input);
return entriesToQuery(entries, idType);
}

View File

@@ -35,6 +35,7 @@ import { OperatorHklColorThemeProvider } from './color/operator-hkl';
import { PartialChargeColorThemeProvider } from './color/partial-charge';
import { AtomIdColorThemeProvider } from './color/atom-id';
import { EntityIdColorThemeProvider } from './color/entity-id';
import { TextureFilter } from '../mol-gl/webgl/texture';
export type LocationColor = (location: Location, isSecondary: boolean) => Color
@@ -44,6 +45,9 @@ interface ColorTheme<P extends PD.Params> {
readonly granularity: ColorType
readonly color: LocationColor
readonly props: Readonly<PD.Values<P>>
// if palette is defined, 24bit RGB color value normalized to interval [0, 1]
// is used as index to the colors
readonly palette?: Readonly<ColorTheme.Palette>
readonly contextHash?: number
readonly description?: string
readonly legend?: Readonly<ScaleLegend | TableLegend>
@@ -58,6 +62,13 @@ namespace ColorTheme {
Misc = 'Miscellaneous',
}
export interface Palette {
filter?: TextureFilter,
colors: Color[]
}
export const PaletteScale = (1 << 24) - 1;
export type Props = { [k: string]: any }
export type Factory<P extends PD.Params> = (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P>
export const EmptyFactory = () => Empty;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,16 +10,16 @@ import { SyncRuntimeContext } from '../../mol-task/execution/synchronous';
describe('zip', () => {
it('roundtrip deflate/inflate', async () => {
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7]);
const deflated = deflate(data);
const deflated = await deflate(SyncRuntimeContext, data);
const inflated = await inflate(SyncRuntimeContext, deflated);
expect(inflated).toEqual(data);
});
it('roundtrip zip', async () => {
it('roundtrip zip/unzip', async () => {
const data = {
'test.foo': new Uint8Array([1, 2, 3, 4, 5, 6, 7])
};
const zipped = zip(data);
const zipped = await zip(SyncRuntimeContext, data);
const unzipped = await unzip(SyncRuntimeContext, zipped);
expect(unzipped).toEqual(data);
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
@@ -7,65 +7,54 @@
* MIT License, Copyright (c) 2018 Photopea
*/
import { RuntimeContext } from '../../mol-task';
import { NumberArray } from '../type-helpers';
import { _hufTree } from './huffman';
import { U, revCodes, makeCodes } from './util';
export function _deflateRaw(data: Uint8Array, out: Uint8Array, opos: number, lvl: number) {
const opts = [
/*
ush good_length; /* reduce lazy search above this match length
ush max_lazy; /* do not perform lazy search above this match length
ush nice_length; /* quit search above this match length
*/
/* good lazy nice chain */
/* 0 */ [ 0, 0, 0, 0, 0], /* store only */
/* 1 */ [ 4, 4, 8, 4, 0], /* max speed, no lazy matches */
/* 2 */ [ 4, 5, 16, 8, 0],
/* 3 */ [ 4, 6, 16, 16, 0],
/* 4 */ [ 4, 10, 16, 32, 0], /* lazy matches */
/* 5 */ [ 8, 16, 32, 32, 0],
/* 6 */ [ 8, 16, 128, 128, 0],
/* 7 */ [ 8, 32, 128, 256, 0],
/* 8 */ [32, 128, 258, 1024, 1],
/* 9 */ [32, 258, 258, 4096, 1] /* max compression */
];
const opt = opts[lvl];
let i = 0, pos = opos << 3, cvrd = 0;
const dlen = data.length;
if(lvl === 0) {
while(i < dlen) {
const len = Math.min(0xffff, dlen - i);
_putsE(out, pos, (i + len === dlen ? 1 : 0));
pos = _copyExact(data, i, len, out, pos + 8);
i += len;
}
return pos >>> 3;
}
function DeflateContext(data: Uint8Array, out: Uint8Array, opos: number, lvl: number) {
const { lits, strt, prev } = U;
let li = 0, lc = 0, bs = 0, ebits = 0, c = 0, nc = 0; // last_item, literal_count, block_start
if(dlen > 2) {
nc = _hash(data, 0);
strt[nc] = 0;
}
return {
data,
out,
opt: Opts[lvl],
i: 0,
pos: opos << 3,
cvrd: 0,
dlen: data.length,
// let nmch = 0
// let nmci = 0
li: 0,
lc: 0,
bs: 0,
ebits: 0,
c: 0,
nc: 0,
for(i = 0; i < dlen; i++) {
lits,
strt,
prev
};
}
type DeflateContext = ReturnType<typeof DeflateContext>
function deflateChunk(ctx: DeflateContext, count: number) {
const { data, dlen, out, opt } = ctx;
let { i, pos, cvrd, li, lc, bs, ebits, c, nc } = ctx;
const { lits, strt, prev } = U;
const end = Math.min(i + count, dlen);
for(; i < end; i++) {
c = nc;
//*
if(i + 1 < dlen - 2) {
nc = _hash(data, i + 1);
const ii = ((i + 1) & 0x7fff);
prev[ii] = strt[nc];
strt[nc] = ii;
} // */
}
if(cvrd <= i) {
if((li > 14000 || lc > 26697) && (dlen - i) > 100) {
if(cvrd < i) {
@@ -79,19 +68,12 @@ export function _deflateRaw(data: Uint8Array, out: Uint8Array, opos: number, lvl
}
let mch = 0;
// if(nmci==i) mch= nmch; else
if(i < dlen - 2) {
mch = _bestMatch(data, i, prev, c, Math.min(opt[2], dlen - i), opt[3]);
}
/*
if(mch!=0 && opt[4]==1 && (mch>>>16)<opt[1] && i+1<dlen-2) {
nmch = UZIP.F._bestMatch(data, i+1, prev, nc, opt[2], opt[3]); nmci=i+1;
//var mch2 = UZIP.F._bestMatch(data, i+2, prev, nnc); //nmci=i+1;
if((nmch>>>16)>(mch>>>16)) mch=0;
}//*/
// const len = mch>>>16, dst = mch & 0xffff; // if(i-dst<0) throw "e";
if(mch !== 0) {
const len = mch >>> 16, dst = mch & 0xffff; // if(i-dst<0) throw "e";
const len = mch >>> 16, dst = mch & 0xffff;
const lgi = _goodIndex(len, U.of0); U.lhst[257 + lgi]++;
const dgi = _goodIndex(dst, U.df0); U.dhst[ dgi]++; ebits += U.exb[lgi] + U.dxb[dgi];
lits[li] = (len << 23) | (i - cvrd); lits[li + 1] = (dst << 16) | (lgi << 8) | dgi; li += 2;
@@ -102,6 +84,69 @@ export function _deflateRaw(data: Uint8Array, out: Uint8Array, opos: number, lvl
lc++;
}
}
ctx.i = i;
ctx.pos = pos;
ctx.cvrd = cvrd;
ctx.li = li;
ctx.lc = lc;
ctx.bs = bs;
ctx.ebits = ebits;
ctx.c = c;
ctx.nc = nc;
}
/**
* - good_length: reduce lazy search above this match length;
* - max_lazy: do not perform lazy search above this match length;
* - nice_length: quit search above this match length;
*/
const Opts = [
/* good lazy nice chain */
/* 0 */ [ 0, 0, 0, 0, 0], /* store only */
/* 1 */ [ 4, 4, 8, 4, 0], /* max speed, no lazy matches */
/* 2 */ [ 4, 5, 16, 8, 0],
/* 3 */ [ 4, 6, 16, 16, 0],
/* 4 */ [ 4, 10, 16, 32, 0], /* lazy matches */
/* 5 */ [ 8, 16, 32, 32, 0],
/* 6 */ [ 8, 16, 128, 128, 0],
/* 7 */ [ 8, 32, 128, 256, 0],
/* 8 */ [32, 128, 258, 1024, 1],
/* 9 */ [32, 258, 258, 4096, 1] /* max compression */
] as const;
export async function _deflateRaw(runtime: RuntimeContext, data: Uint8Array, out: Uint8Array, opos: number, lvl: number) {
const ctx = DeflateContext(data, out, opos, lvl);
const { dlen } = ctx;
if(lvl === 0) {
let { i, pos } = ctx;
while(i < dlen) {
const len = Math.min(0xffff, dlen - i);
_putsE(out, pos, (i + len === dlen ? 1 : 0));
pos = _copyExact(data, i, len, out, pos + 8);
i += len;
}
return pos >>> 3;
}
if(dlen > 2) {
ctx.nc = _hash(data, 0);
ctx.strt[ctx.nc] = 0;
}
while (ctx.i < dlen) {
if (runtime.shouldUpdate) {
await runtime.update({ message: 'Deflating...', current: ctx.i, max: dlen });
}
deflateChunk(ctx, 1024 * 1024);
}
let { li, cvrd, pos } = ctx;
const { i, lits, bs, ebits } = ctx;
if(bs !== i || data.length === 0) {
if(cvrd < i) {
lits[li] = i - cvrd;
@@ -109,10 +154,6 @@ export function _deflateRaw(data: Uint8Array, out: Uint8Array, opos: number, lvl
cvrd = i;
}
pos = _writeBlock(1, lits, li, ebits, data, bs, i - bs, out, pos);
li = 0;
lc = 0;
li = lc = ebits = 0;
bs = i;
}
while((pos & 7) !== 0) pos++;
return pos >>> 3;

View File

@@ -13,7 +13,7 @@ import { writeUint, writeUshort, sizeUTF8, writeUTF8, readUshort, readUint, read
import { crc, adler } from './checksum';
import { _inflate } from './inflate';
import { _deflateRaw } from './deflate';
import { RuntimeContext } from '../../mol-task';
import { RuntimeContext, Task } from '../../mol-task';
export async function unzip(runtime: RuntimeContext, buf: ArrayBuffer, onlyNames = false) {
const out: { [k: string]: Uint8Array | { size: number, csize: number } } = Object.create(null);
@@ -174,12 +174,12 @@ export async function ungzip(runtime: RuntimeContext, file: Uint8Array, buf?: Ui
return inflated;
}
export function deflate(data: Uint8Array, opts?: { level: number }/* , buf, off*/) {
export async function deflate(runtime: RuntimeContext, data: Uint8Array, opts?: { level: number }/* , buf, off*/) {
if(opts === undefined) opts = { level: 6 };
let off = 0;
const buf = new Uint8Array(50 + Math.floor(data.length * 1.1));
buf[off] = 120; buf[off + 1] = 156; off += 2;
off = _deflateRaw(data, buf, off, opts.level);
off = await _deflateRaw(runtime, data, buf, off, opts.level);
const crcValue = adler(data, 0, data.length);
buf[off + 0] = ((crcValue >>> 24) & 255);
buf[off + 1] = ((crcValue >>> 16) & 255);
@@ -188,14 +188,18 @@ export function deflate(data: Uint8Array, opts?: { level: number }/* , buf, off*
return new Uint8Array(buf.buffer, 0, off + 4);
}
function deflateRaw(data: Uint8Array, opts?: { level: number }) {
async function deflateRaw(runtime: RuntimeContext, data: Uint8Array, opts?: { level: number }) {
if(opts === undefined) opts = { level: 6 };
const buf = new Uint8Array(50 + Math.floor(data.length * 1.1));
const off = _deflateRaw(data, buf, 0, opts.level);
const off = await _deflateRaw(runtime, data, buf, 0, opts.level);
return new Uint8Array(buf.buffer, 0, off);
}
export function zip(obj: { [k: string]: Uint8Array }, noCmpr = false) {
export function Zip(obj: { [k: string]: Uint8Array }, noCmpr = false) {
return Task.create('Zip', ctx => zip(ctx, obj, noCmpr));
}
export async function zip(runtime: RuntimeContext, obj: { [k: string]: Uint8Array }, noCmpr = false) {
let tot = 0;
const zpd: { [k: string]: { cpr: boolean, usize: number, crc: number, file: Uint8Array } } = {};
for(const p in obj) {
@@ -205,7 +209,7 @@ export function zip(obj: { [k: string]: Uint8Array }, noCmpr = false) {
cpr,
usize: buf.length,
crc: crcValue,
file: (cpr ? deflateRaw(buf) : buf)
file: (cpr ? await deflateRaw(runtime, buf) : buf)
};
}

View File

@@ -105,7 +105,12 @@ async function init() {
const mcBoundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox);
const mcIsosurface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, mcBoundingSphere);
const mcIsoSurfaceProps = { doubleSided: true, flatShaded: true, alpha: 1.0 };
const mcIsoSurfaceProps = {
...PD.getDefaultValues(TextureMesh.Params),
doubleSided: true,
flatShaded: true,
alpha: 1.0
};
const mcIsoSurfaceValues = TextureMesh.Utils.createValuesSimple(mcIsosurface, mcIsoSurfaceProps, Color(0x112299), 1);
// console.log('mcIsoSurfaceValues', mcIsoSurfaceValues)
const mcIsoSurfaceState = TextureMesh.Utils.createRenderableState(mcIsoSurfaceProps);
@@ -133,7 +138,12 @@ async function init() {
console.timeEnd('cpu mc');
console.log('surface', surface);
Mesh.transform(surface, densityData.transform);
const meshProps = { doubleSided: true, flatShaded: false, alpha: 1.0 };
const meshProps = {
...PD.getDefaultValues(Mesh.Params),
doubleSided: true,
flatShaded: false,
alpha: 1.0
};
const meshValues = Mesh.Utils.createValuesSimple(surface, meshProps, Color(0x995511), 1);
const meshState = Mesh.Utils.createRenderableState(meshProps);
const meshRenderObject = createRenderObject('mesh', meshValues, meshState, -1);

View File

@@ -14,6 +14,7 @@ import { Lines } from '../../mol-geo/geometry/lines/lines';
import { Color } from '../../mol-util/color';
import { createRenderObject } from '../../mol-gl/render-object';
import { Representation } from '../../mol-repr/representation';
import { ParamDefinition } from '../../mol-util/param-definition';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -33,8 +34,9 @@ function linesRepr() {
linesBuilder.addCage(t, dodecahedronCage, 0);
const lines = linesBuilder.getLines();
const values = Lines.Utils.createValuesSimple(lines, {}, Color(0xFF0000), 3);
const state = Lines.Utils.createRenderableState({});
const props = ParamDefinition.getDefaultValues(Lines.Utils.Params);
const values = Lines.Utils.createValuesSimple(lines, props, Color(0xFF0000), 3);
const state = Lines.Utils.createRenderableState(props);
const renderObject = createRenderObject('lines', values, state, -1);
const repr = Representation.fromRenderObject('cage-lines', renderObject);
return repr;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -15,6 +15,8 @@ import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { Color } from '../../mol-util/color';
import { createRenderObject } from '../../mol-gl/render-object';
import { Representation } from '../../mol-repr/representation';
import { Torus } from '../../mol-geo/primitive/torus';
import { ParamDefinition } from '../../mol-util/param-definition';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -31,17 +33,24 @@ function meshRepr() {
const builderState = MeshBuilder.createState();
const t = Mat4.identity();
MeshBuilder.addCage(builderState, t, HexagonalPrismCage(), 0.005, 2, 20);
Mat4.scaleUniformly(t, t, 10);
MeshBuilder.addCage(builderState, t, HexagonalPrismCage(), 0.05, 2, 20);
const t2 = Mat4.identity();
Mat4.scaleUniformly(t2, t2, 0.1);
Mat4.scaleUniformly(t2, t2, 1);
MeshBuilder.addPrimitive(builderState, t2, SpikedBall(3));
const t3 = Mat4.identity();
Mat4.scaleUniformly(t3, t3, 8);
MeshBuilder.addPrimitive(builderState, t3, Torus({ tubularSegments: 64, radialSegments: 32, tube: 0.1 }));
const mesh = MeshBuilder.getMesh(builderState);
const values = Mesh.Utils.createValuesSimple(mesh, {}, Color(0xFF0000), 1);
const state = Mesh.Utils.createRenderableState({});
const props = ParamDefinition.getDefaultValues(Mesh.Utils.Params);
const values = Mesh.Utils.createValuesSimple(mesh, props, Color(0xFF4433), 1);
const state = Mesh.Utils.createRenderableState(props);
const renderObject = createRenderObject('mesh', values, state, -1);
console.log('mesh', renderObject);
const repr = Representation.fromRenderObject('mesh', renderObject);
return repr;
}

View File

@@ -111,6 +111,7 @@ const repr = ShapeRepresentation(getShape, Mesh.Utils);
export async function init() {
// Create shape from myData and add to canvas3d
await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p)));
console.log('shape', repr);
canvas3d.add(repr);
canvas3d.requestCameraReset();

View File

@@ -12,6 +12,7 @@ import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { Color } from '../../mol-util/color';
import { createRenderObject } from '../../mol-gl/render-object';
import { Representation } from '../../mol-repr/representation';
import { ParamDefinition } from '../../mol-util/param-definition';
const parent = document.getElementById('app')!;
parent.style.width = '100%';
@@ -31,8 +32,9 @@ function spheresRepr() {
spheresBuilder.add(-4, 1, 0, 0);
const spheres = spheresBuilder.getSpheres();
const props = ParamDefinition.getDefaultValues(Spheres.Utils.Params);
const values = Spheres.Utils.createValuesSimple(spheres, {}, Color(0xFF0000), 1);
const state = Spheres.Utils.createRenderableState({});
const state = Spheres.Utils.createRenderableState(props);
const renderObject = createRenderObject('spheres', values, state, -1);
console.log(renderObject);
const repr = Representation.fromRenderObject('spheres', renderObject);

View File

@@ -8,7 +8,7 @@ import './index.html';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { TextBuilder } from '../../mol-geo/geometry/text/text-builder';
import { Text } from '../../mol-geo/geometry/text/text';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ParamDefinition, ParamDefinition as PD } from '../../mol-util/param-definition';
import { Color } from '../../mol-util/color';
import { Representation } from '../../mol-repr/representation';
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
@@ -64,8 +64,9 @@ function spheresRepr() {
spheresBuilder.add(-4, 1, 0, 0);
const spheres = spheresBuilder.getSpheres();
const values = Spheres.Utils.createValuesSimple(spheres, {}, Color(0xFF0000), 0.2);
const state = Spheres.Utils.createRenderableState({});
const props = ParamDefinition.getDefaultValues(Spheres.Utils.Params);
const values = Spheres.Utils.createValuesSimple(spheres, props, Color(0xFF0000), 0.2);
const state = Spheres.Utils.createRenderableState(props);
const renderObject = createRenderObject('spheres', values, state, -1);
console.log('spheres', renderObject);
const repr = Representation.fromRenderObject('spheres', renderObject);