mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 15:14:22 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eb351369e | ||
|
|
701d782485 | ||
|
|
dc8457c4dc | ||
|
|
f104cd4d11 | ||
|
|
9b56a6ae65 | ||
|
|
2485ad5a2f | ||
|
|
a56716ab6a | ||
|
|
e0aaaa989e | ||
|
|
9ec0f9e736 | ||
|
|
47968eeeec | ||
|
|
9c157b70e1 | ||
|
|
6d7e4ca227 | ||
|
|
fccd08d2ec | ||
|
|
19bae202d0 | ||
|
|
4ba0ae24e4 | ||
|
|
fcf3718d75 | ||
|
|
df1dd94f1c | ||
|
|
65ba401850 | ||
|
|
a98f5e1047 | ||
|
|
e5cf97d1ea | ||
|
|
1844fc14b2 | ||
|
|
d185c0ef34 | ||
|
|
40a4211e75 | ||
|
|
daa2bbd042 | ||
|
|
ed5b4b27a8 | ||
|
|
408ccb4353 | ||
|
|
99e3cd6654 | ||
|
|
0819ace1dc | ||
|
|
987c9210bd | ||
|
|
84fb42a161 | ||
|
|
53d3480701 | ||
|
|
eb629ef337 | ||
|
|
c26111e8fb | ||
|
|
4853ff7a1a |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -3,8 +3,22 @@ All notable changes to this project will be documented in this file, following t
|
||||
|
||||
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]
|
||||
|
||||
|
||||
## [v2.1.0] - 2021-07-05
|
||||
|
||||
- Add parameter for to display aromatic bonds as dashes next to solid cylinder/line.
|
||||
- Add backbone representation
|
||||
- Fix outline in orthographic mode and set default scale to 2.
|
||||
|
||||
## [v2.0.7] - 2021-06-23
|
||||
|
||||
- Add ability to specify ``volumeIndex`` in ``Viewer.loadVolumeFromUrl`` to better support Volume Server inputs.
|
||||
- Support in-place reordering for trajectory ``Frame.x/y/z`` arrays for better memory efficiency.
|
||||
- Fixed text CIF encoder edge cases (most notably single whitespace not being escaped).
|
||||
|
||||
## [v2.0.6] - 2021-06-01
|
||||
|
||||
- Add glTF (GLB) and STL support to ``geo-export`` extension.
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.6",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.6",
|
||||
"version": "2.1.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -38,19 +38,6 @@
|
||||
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>
|
||||
|
||||
@@ -243,7 +243,46 @@ export class Viewer {
|
||||
}));
|
||||
}
|
||||
|
||||
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string, isLazy?: boolean }) {
|
||||
/**
|
||||
* @example Load X-ray density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1.5,
|
||||
color: 0x3362B2
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: 3,
|
||||
color: 0x33BB33,
|
||||
volumeIndex: 1
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: -3,
|
||||
color: 0xBB3333,
|
||||
volumeIndex: 1
|
||||
}], {
|
||||
entryId: ['2FO-FC', 'FO-FC'],
|
||||
isLazy: true
|
||||
});
|
||||
* *********************
|
||||
* @example Load EM density from volume server
|
||||
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
|
||||
});
|
||||
*/
|
||||
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!plugin.dataFormats.get(format)) {
|
||||
@@ -257,26 +296,28 @@ export class Viewer {
|
||||
format,
|
||||
entryId: options?.entryId,
|
||||
isBinary,
|
||||
isovalues: isovalues.map(v => ({ alpha: 1, ...v }))
|
||||
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
|
||||
});
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary, label: options?.entryId }, { state: { isGhost: true } });
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { 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.');
|
||||
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const repr = plugin.build().to(volume);
|
||||
const repr = plugin.build();
|
||||
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 }
|
||||
}));
|
||||
repr
|
||||
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.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();
|
||||
@@ -296,5 +337,6 @@ export interface VolumeIsovalueInfo {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number
|
||||
alpha?: number,
|
||||
volumeIndex?: number
|
||||
}
|
||||
@@ -181,6 +181,6 @@ export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramid
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ConfalPyramidsProvider.descriptor, false)
|
||||
detach: (data) => data.structure && ConfalPyramidsProvider.ref(data.structure.models[0], false)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { getStyle } from '../../mol-gl/renderer';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
@@ -43,17 +44,18 @@ export class GeometryControls extends PluginComponent {
|
||||
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
|
||||
const filename = this.getFilename();
|
||||
|
||||
let renderObjectExporter: ObjExporter | GlbExporter | StlExporter;
|
||||
const boundingBox = Box3D.fromSphere3D(Box3D(), this.plugin.canvas3d?.boundingSphereVisible!);
|
||||
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter;
|
||||
switch (this.behaviors.params.value.format) {
|
||||
case 'obj':
|
||||
renderObjectExporter = new ObjExporter(filename);
|
||||
break;
|
||||
case 'glb':
|
||||
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
|
||||
renderObjectExporter = new GlbExporter(style);
|
||||
renderObjectExporter = new GlbExporter(style, boundingBox);
|
||||
break;
|
||||
case 'obj':
|
||||
renderObjectExporter = new ObjExporter(filename, boundingBox);
|
||||
break;
|
||||
case 'stl':
|
||||
renderObjectExporter = new StlExporter();
|
||||
renderObjectExporter = new StlExporter(boundingBox);
|
||||
break;
|
||||
default: throw new Error('Unsupported format.');
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { BaseValues } from '../../mol-gl/renderable/schema';
|
||||
import { Style } from '../../mol-gl/renderer';
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
@@ -41,6 +42,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
private bufferViews: Record<string, any>[] = [];
|
||||
private binaryBuffer: ArrayBuffer[] = [];
|
||||
private byteOffset = 0;
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private static vec3MinMax(a: NumberArray) {
|
||||
const min: number[] = [Infinity, Infinity, Infinity];
|
||||
@@ -252,11 +254,12 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
}
|
||||
|
||||
// node
|
||||
const node: Record<string, any> = {
|
||||
mesh: meshIndex!
|
||||
};
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
if (!Mat4.isIdentity(t)) node.matrix = t.slice();
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
const node: Record<string, any> = {
|
||||
mesh: meshIndex!,
|
||||
matrix: t.slice()
|
||||
};
|
||||
this.nodes.push(node);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +337,11 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
return new Blob([this.getData().glb], { type: 'model/gltf-binary' });
|
||||
}
|
||||
|
||||
constructor(private style: Style) {
|
||||
constructor(private style: Style, boundingBox: Box3D) {
|
||||
super();
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
Vec3.scale(tmpV, tmpV, -0.5);
|
||||
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { sort, arraySwap } from '../../mol-data/util';
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
@@ -35,6 +36,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
private currentColor: Color | undefined;
|
||||
private currentAlpha: number | undefined;
|
||||
private materialSet = new Set<string>();
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private updateMaterial(color: Color, alpha: number) {
|
||||
if (this.currentColor === color && this.currentAlpha === alpha) return;
|
||||
@@ -163,6 +165,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
const { vertices, normals, indices, groups, vertexCount, drawCount } = ObjExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
mat3directionTransform(n, t);
|
||||
|
||||
// position
|
||||
@@ -273,8 +276,12 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
return new Blob([await zip(ctx, zipDataObj)], { type: 'application/zip' });
|
||||
}
|
||||
|
||||
constructor(private filename: string) {
|
||||
constructor(private filename: string, boundingBox: Box3D) {
|
||||
super();
|
||||
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
Vec3.scale(tmpV, tmpV, -0.5);
|
||||
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
@@ -26,6 +27,7 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
readonly fileExtension = 'stl';
|
||||
private triangleBuffers: ArrayBuffer[] = [];
|
||||
private triangleCount = 0;
|
||||
private centerTransform: Mat4;
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { values, isGeoTexture, ctx } = input;
|
||||
@@ -46,6 +48,7 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
const { vertices, indices, vertexCount, drawCount } = StlExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
|
||||
// position
|
||||
const vertexArray = new Float32Array(vertexCount * 3);
|
||||
@@ -105,4 +108,12 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([this.getData().stl], { type: 'model/stl' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D) {
|
||||
super();
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
Vec3.scale(tmpV, tmpV, -0.5);
|
||||
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,6 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
|
||||
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && StructureQualityReportProvider.ref(data.structure.models[0], false)
|
||||
}
|
||||
};
|
||||
@@ -109,6 +109,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
detach: (data) => data.structure && AssemblySymmetryProvider.ref(data.structure, false)
|
||||
}
|
||||
};
|
||||
@@ -77,6 +77,6 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromXray(ctx.structure.models[0]) && Model.probablyHasDensityMap(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
|
||||
}
|
||||
};
|
||||
@@ -115,6 +115,6 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
|
||||
}
|
||||
};
|
||||
@@ -68,6 +68,6 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromNmr(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && ValidationReportProvider.ref(data.structure.models[0], false)
|
||||
}
|
||||
};
|
||||
@@ -364,8 +364,9 @@ function updateClip(camera: Camera) {
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
} else {
|
||||
near = Math.max(0, near);
|
||||
far = Math.max(0, far);
|
||||
// not too close to 0 as it causes issues with outline rendering
|
||||
near = Math.max(Math.min(radiusMax, 5), near);
|
||||
far = Math.max(5, far);
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
|
||||
@@ -242,6 +242,7 @@ interface Canvas3D {
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Camera.SnapshotProvider }): void
|
||||
readonly camera: Camera
|
||||
readonly boundingSphere: Readonly<Sphere3D>
|
||||
readonly boundingSphereVisible: Readonly<Sphere3D>
|
||||
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
|
||||
getImagePass(props: Partial<ImageProps>): ImagePass
|
||||
getRenderObjects(): GraphicsRenderObject[]
|
||||
@@ -717,6 +718,7 @@ namespace Canvas3D {
|
||||
},
|
||||
camera,
|
||||
boundingSphere: scene.boundingSphere,
|
||||
boundingSphereVisible: scene.boundingSphereVisible,
|
||||
get notifyDidDraw() { return notifyDidDraw; },
|
||||
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
|
||||
didDraw,
|
||||
|
||||
@@ -290,7 +290,7 @@ export class DrawPass {
|
||||
renderer.setViewport(x, y, width, height);
|
||||
renderer.update(camera);
|
||||
|
||||
if (transparentBackground) {
|
||||
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
|
||||
this.drawTarget.bind();
|
||||
renderer.clear(false);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -103,7 +103,9 @@ export class ImagePass {
|
||||
} else {
|
||||
this.webgl.readPixels(viewport.x, height - viewport.y - viewport.height, w, h, array);
|
||||
}
|
||||
PixelData.flipY({ array, width: w, height: h });
|
||||
const pixelData = PixelData.create(array, w, h);
|
||||
PixelData.flipY(pixelData);
|
||||
PixelData.divideByAlpha(pixelData);
|
||||
return new ImageData(new Uint8ClampedArray(array), w, h);
|
||||
}
|
||||
}
|
||||
@@ -250,7 +250,7 @@ export const PostprocessingParams = {
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
outline: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
|
||||
scale: PD.Numeric(2, { min: 1, max: 5, step: 1 }),
|
||||
threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
@@ -306,7 +306,8 @@ export class PostprocessingPass {
|
||||
this.nSamples = 1;
|
||||
this.blurKernelSize = 1;
|
||||
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'nearest');
|
||||
// needs to be linear for anti-aliasing pass
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
|
||||
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
|
||||
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
|
||||
@@ -433,8 +434,12 @@ export class PostprocessingPass {
|
||||
}
|
||||
|
||||
if (props.outline.name === 'on') {
|
||||
const factor = Math.pow(1000, props.outline.params.threshold) / 1000;
|
||||
const maxPossibleViewZDiff = factor * (camera.far - camera.near);
|
||||
let { threshold } = props.outline.params;
|
||||
// orthographic needs lower threshold
|
||||
if (camera.state.mode === 'orthographic') threshold /= 5;
|
||||
const factor = Math.pow(1000, threshold) / 1000;
|
||||
// use radiusMax for stable outlines when zooming
|
||||
const maxPossibleViewZDiff = factor * camera.state.radiusMax;
|
||||
const outlineScale = props.outline.params.scale - 1;
|
||||
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
|
||||
@@ -442,7 +447,6 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
|
||||
|
||||
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
|
||||
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number, matchDi
|
||||
// ensure the direction used to create the rotation is always pointing in the same
|
||||
// direction so the triangles of adjacent cylinder will line up
|
||||
if (matchDir) Vec3.matchDirection(tmpUp, up, tmpCylinderMatDir);
|
||||
else Vec3.copy(tmpUp, up);
|
||||
Mat4.fromScaling(tmpCylinderMatScale, Vec3.set(tmpCylinderScale, 1, length, 1));
|
||||
Vec3.makeRotation(tmpCylinderMatRot, tmpUp, tmpCylinderMatDir);
|
||||
Mat4.mul(m, tmpCylinderMatRot, tmpCylinderMatScale);
|
||||
|
||||
@@ -26,9 +26,6 @@ uniform bool uTransparentBackground;
|
||||
uniform float uOcclusionBias;
|
||||
uniform float uOcclusionRadius;
|
||||
|
||||
uniform float uOutlineScale;
|
||||
uniform float uOutlineThreshold;
|
||||
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
const vec3 occlusionColor = vec3(0.0);
|
||||
|
||||
@@ -186,6 +186,7 @@ export interface Texture {
|
||||
readonly format: number
|
||||
readonly internalFormat: number
|
||||
readonly type: number
|
||||
readonly filter: number
|
||||
|
||||
getWidth: () => number
|
||||
getHeight: () => number
|
||||
@@ -326,6 +327,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
format,
|
||||
internalFormat,
|
||||
type,
|
||||
filter,
|
||||
|
||||
getWidth: () => width,
|
||||
getHeight: () => height,
|
||||
@@ -415,6 +417,7 @@ export function createNullTexture(gl?: GLRenderingContext): Texture {
|
||||
format: 0,
|
||||
internalFormat: 0,
|
||||
type: 0,
|
||||
filter: 0,
|
||||
|
||||
getWidth: () => 0,
|
||||
getHeight: () => 0,
|
||||
|
||||
@@ -3,7 +3,10 @@ import { CifWriter } from '../cif';
|
||||
import { decodeMsgPack } from '../../common/msgpack/decode';
|
||||
import { EncodedFile, EncodedCategory } from '../../common/binary-cif';
|
||||
import { Field } from '../../reader/cif/binary/field';
|
||||
import { TextEncoder } from '../cif/encoder/text';
|
||||
import * as C from '../cif/encoder';
|
||||
import { Column, Database, Table } from '../../../mol-data/db';
|
||||
import { parseCifText } from '../../reader/cif/text/parser';
|
||||
|
||||
const cartn_x = Data.CifField.ofNumbers([1.001, 1.002, 1.003, 1.004, 1.005, 1.006, 1.007, 1.008, 1.009]);
|
||||
const cartn_y = Data.CifField.ofNumbers([-3.0, -2.666, -2.3333, -2.0, -1.666, -1.333, -1.0, -0.666, -0.333]);
|
||||
@@ -38,6 +41,30 @@ const encoding_aware_encoder = CifWriter.createEncoder({
|
||||
])
|
||||
});
|
||||
|
||||
test('cif writer value escaping', async () => {
|
||||
const values = ['1', ' ', ' ', ` ' `, `a'`, `b"`, `"`, ' a ', `"'"`, `'"`, `\na`];
|
||||
const table = Table.ofArrays({ values: Column.Schema.str }, { values });
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
C.Encoder.writeDatabase(encoder, 'test', Database.ofTables('test', { test: table._schema }, { test: table }));
|
||||
encoder.encode();
|
||||
const data = encoder.getData();
|
||||
|
||||
const result = await parseCifText(data).run();
|
||||
if (result.isError) {
|
||||
expect(false).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const cat = result.result.blocks[0].categories['test'];
|
||||
const parsed = cat.getField('values')?.toStringArray();
|
||||
|
||||
expect(values.length).toBe(parsed?.length);
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
expect(values[i]).toBe(parsed?.[i]);
|
||||
}
|
||||
});
|
||||
|
||||
describe('encoding-config', () => {
|
||||
const decoded = process(encoding_aware_encoder);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Adapted from CIFTools.js (https://github.com/dsehnal/CIFTools.js)
|
||||
*
|
||||
@@ -208,55 +208,61 @@ function writeChecked(builder: StringBuilder, val: string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let escape = val.charCodeAt(0) === 95 /* _ */, escapeCharStart = '\'', escapeCharEnd = '\' ';
|
||||
let hasWhitespace = false;
|
||||
let hasSingle = false;
|
||||
let hasDouble = false;
|
||||
for (let i = 0, _l = val.length - 1; i < _l; i++) {
|
||||
const fst = val.charCodeAt(0);
|
||||
let escape = false;
|
||||
let escapeKind = 0; // 0 => ', 1 => "
|
||||
let hasSingleQuote = false, hasDoubleQuote = false;
|
||||
for (let i = 0, _l = val.length - 1; i <= _l; i++) {
|
||||
const c = val.charCodeAt(i);
|
||||
|
||||
switch (c) {
|
||||
case 9: hasWhitespace = true; break; // \t
|
||||
case 9: // \t
|
||||
escape = true;
|
||||
break;
|
||||
case 10: // \n
|
||||
writeMultiline(builder, val);
|
||||
return true;
|
||||
case 32: hasWhitespace = true; break; // ' '
|
||||
case 32: // ' '
|
||||
escape = true;
|
||||
break;
|
||||
case 34: // "
|
||||
if (hasSingle) {
|
||||
// no need to escape quote if it's the last char and the length is > 1
|
||||
if (i && i === _l) break;
|
||||
|
||||
if (hasSingleQuote) {
|
||||
// the string already has a " => use multiline value
|
||||
writeMultiline(builder, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
hasDouble = true;
|
||||
hasDoubleQuote = true;
|
||||
escape = true;
|
||||
escapeCharStart = '\'';
|
||||
escapeCharEnd = '\' ';
|
||||
escapeKind = 0;
|
||||
break;
|
||||
case 39: // '
|
||||
if (hasDouble) {
|
||||
// no need to escape quote if it's the last char and the length is > 1
|
||||
if (i && i === _l) break;
|
||||
|
||||
if (hasDoubleQuote) {
|
||||
writeMultiline(builder, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
hasSingleQuote = true;
|
||||
escape = true;
|
||||
hasSingle = true;
|
||||
escapeCharStart = '"';
|
||||
escapeCharEnd = '" ';
|
||||
escapeKind = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const fst = val.charCodeAt(0);
|
||||
if (!escape && (fst === 35 /* # */|| fst === 36 /* $ */ || fst === 59 /* ; */ || fst === 91 /* [ */ || fst === 93 /* ] */ || hasWhitespace)) {
|
||||
escapeCharStart = '\'';
|
||||
escapeCharEnd = '\' ';
|
||||
if (!escape && (fst === 35 /* # */ || fst === 36 /* $ */ || fst === 59 /* ; */ || fst === 91 /* [ */ || fst === 93 /* ] */ || fst === 95 /* _ */)) {
|
||||
escape = true;
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
StringBuilder.writeSafe(builder, escapeCharStart);
|
||||
StringBuilder.writeSafe(builder, escapeKind ? '"' : '\'');
|
||||
StringBuilder.writeSafe(builder, val);
|
||||
StringBuilder.writeSafe(builder, escapeCharEnd);
|
||||
StringBuilder.writeSafe(builder, escapeKind ? '" ' : '\' ');
|
||||
} else {
|
||||
StringBuilder.writeSafe(builder, val);
|
||||
StringBuilder.writeSafe(builder, ' ');
|
||||
|
||||
@@ -474,7 +474,9 @@ namespace Vec3 {
|
||||
|
||||
/** Computes the angle between 2 vectors, reports in radians. */
|
||||
export function angle(a: Vec3, b: Vec3) {
|
||||
const theta = dot(a, b) / Math.sqrt(squaredMagnitude(a) * squaredMagnitude(b));
|
||||
const denominator = Math.sqrt(squaredMagnitude(a) * squaredMagnitude(b));
|
||||
if (denominator === 0) return Math.PI / 2;
|
||||
const theta = dot(a, b) / denominator;
|
||||
return Math.acos(clamp(theta, -1, 1)); // clamp to avoid numerical problems
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ export function coordinatesFromDcd(dcdFile: DcdFile): Task<Coordinates> {
|
||||
x: dcdFrame.x,
|
||||
y: dcdFrame.y,
|
||||
z: dcdFrame.z,
|
||||
|
||||
xyzOrdering: { isIdentity: true }
|
||||
};
|
||||
|
||||
if (dcdFrame.cell) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { BondType, MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
@@ -74,8 +74,33 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
if (_models.frameCount > 0) {
|
||||
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
|
||||
const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
|
||||
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => x === 'ar' ? 1 : parseInt(x), Int8Array));
|
||||
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order }, count: atoms.count });
|
||||
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
|
||||
switch (x) {
|
||||
case 'ar': // aromatic
|
||||
case 'am': // amide
|
||||
case 'un': // unknown
|
||||
return 1;
|
||||
case 'du': // dummy
|
||||
case 'nc': // not connected
|
||||
return 0;
|
||||
default:
|
||||
return parseInt(x);
|
||||
}
|
||||
}, Int8Array));
|
||||
const flag = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
|
||||
switch (x) {
|
||||
case 'ar': // aromatic
|
||||
return BondType.Flag.Aromatic | BondType.Flag.Covalent;
|
||||
case 'du': // dummy
|
||||
case 'nc': // not connected
|
||||
return BondType.Flag.None;
|
||||
case 'am': // amide
|
||||
case 'un': // unknown
|
||||
default:
|
||||
return BondType.Flag.Covalent;
|
||||
}
|
||||
}, Int8Array));
|
||||
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order, flag }, count: atoms.count });
|
||||
|
||||
const first = _models.representative;
|
||||
IndexPairBonds.Provider.set(first, pairBonds);
|
||||
|
||||
@@ -30,6 +30,7 @@ export function coordinatesFromXtc(file: XtcFile): Task<Coordinates> {
|
||||
x: file.frames[i].x,
|
||||
y: file.frames[i].y,
|
||||
z: file.frames[i].z,
|
||||
xyzOrdering: { isIdentity: true },
|
||||
time: Time(offsetTime.value + deltaTime.value * i, deltaTime.unit)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,6 +83,6 @@ export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<Access
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
|
||||
detach: (data) => data.structure && AccessibleSurfaceAreaProvider.ref(data.structure, false)
|
||||
}
|
||||
};
|
||||
@@ -117,6 +117,6 @@ export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionT
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? InteractionsProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
|
||||
detach: (data) => data.structure && InteractionsProvider.ref(data.structure, false)
|
||||
}
|
||||
};
|
||||
@@ -70,6 +70,6 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && CrossLinkRestraint.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
|
||||
detach: (data) => data.structure && CrossLinkRestraintProvider.ref(data.structure, false)
|
||||
}
|
||||
};
|
||||
@@ -29,7 +29,7 @@ namespace BestDatabaseSequenceMapping {
|
||||
type: 'static',
|
||||
defaultParams: {},
|
||||
getParams: () => ({}),
|
||||
isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site.fieldNames.indexOf('db_name') >= 0,
|
||||
isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site?.fieldNames.indexOf('db_name') >= 0,
|
||||
obtain: async (ctx, data) => {
|
||||
return { value: fromCif(data) };
|
||||
}
|
||||
@@ -92,4 +92,4 @@ namespace BestDatabaseSequenceMapping {
|
||||
|
||||
return { dbName, accession, num, residue };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export const BestDatabaseSequenceMappingColorThemeProvider: ColorTheme.Provider<
|
||||
detach: (data) => {
|
||||
if (!data.structure) return;
|
||||
for (const m of data.structure.models) {
|
||||
m.customProperties.reference(BestDatabaseSequenceMapping.Provider.descriptor, false);
|
||||
BestDatabaseSequenceMapping.Provider.ref(m, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* 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 David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { arrayEqual, UUID } from '../../../mol-util';
|
||||
import { Cell } from '../../../mol-math/geometry/spacegroup/cell';
|
||||
import { AtomicConformation } from '../model/properties/atomic';
|
||||
import { Column } from '../../../mol-data/db';
|
||||
@@ -34,6 +35,12 @@ export interface Frame {
|
||||
readonly fy: ArrayLike<number>
|
||||
readonly fz: ArrayLike<number>
|
||||
}
|
||||
|
||||
readonly xyzOrdering: {
|
||||
isIdentity: boolean,
|
||||
frozen?: boolean,
|
||||
index?: ArrayLike<number>,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -85,41 +92,106 @@ namespace Coordinates {
|
||||
hasVelocities,
|
||||
hasForces,
|
||||
deltaTime,
|
||||
timeOffset
|
||||
timeOffset,
|
||||
};
|
||||
}
|
||||
|
||||
export function getAtomicConformation(frame: Frame, atomId: Column<number>): AtomicConformation {
|
||||
/**
|
||||
* Only use ordering if it's not identity.
|
||||
*/
|
||||
export function getAtomicConformation(frame: Frame, atomId: Column<number>, ordering?: ArrayLike<number>): AtomicConformation {
|
||||
let { x, y, z } = frame;
|
||||
|
||||
if (frame.xyzOrdering.frozen) {
|
||||
if (ordering) {
|
||||
if (frame.xyzOrdering.isIdentity) {
|
||||
// simple list reordering
|
||||
x = getOrderedCoords(x, ordering);
|
||||
y = getOrderedCoords(y, ordering);
|
||||
z = getOrderedCoords(z, ordering);
|
||||
} else if (!arrayEqual(frame.xyzOrdering.index! as any, ordering as any)) {
|
||||
x = getSourceOrderedCoords(x, frame.xyzOrdering.index!, ordering);
|
||||
y = getSourceOrderedCoords(y, frame.xyzOrdering.index!, ordering);
|
||||
z = getSourceOrderedCoords(z, frame.xyzOrdering.index!, ordering);
|
||||
}
|
||||
} else if (!frame.xyzOrdering.isIdentity) {
|
||||
x = getInvertedCoords(x, frame.xyzOrdering.index!);
|
||||
y = getInvertedCoords(y, frame.xyzOrdering.index!);
|
||||
z = getInvertedCoords(z, frame.xyzOrdering.index!);
|
||||
}
|
||||
} else if (ordering) {
|
||||
if (frame.xyzOrdering.isIdentity) {
|
||||
frame.xyzOrdering.isIdentity = false;
|
||||
frame.xyzOrdering.index = ordering;
|
||||
reorderCoordsInPlace(x as unknown as number[], ordering);
|
||||
reorderCoordsInPlace(y as unknown as number[], ordering);
|
||||
reorderCoordsInPlace(z as unknown as number[], ordering);
|
||||
} else {
|
||||
// is current ordering is not the same as requested?
|
||||
// => copy the conformations into a new array
|
||||
if (!arrayEqual(frame.xyzOrdering.index! as any, ordering as any)) {
|
||||
x = getSourceOrderedCoords(x, frame.xyzOrdering.index!, ordering);
|
||||
y = getSourceOrderedCoords(y, frame.xyzOrdering.index!, ordering);
|
||||
z = getSourceOrderedCoords(z, frame.xyzOrdering.index!, ordering);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// once the conformation has been accessed at least once, freeze it.
|
||||
// => any other request to the frame with different ordering will result in a copy.
|
||||
frame.xyzOrdering.frozen = true;
|
||||
|
||||
return {
|
||||
id: UUID.create22(),
|
||||
atomId,
|
||||
occupancy: Column.ofConst(1, frame.elementCount, Column.Schema.int),
|
||||
B_iso_or_equiv: Column.ofConst(0, frame.elementCount, Column.Schema.float),
|
||||
xyzDefined: true,
|
||||
x: frame.x,
|
||||
y: frame.y,
|
||||
z: frame.z,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
};
|
||||
}
|
||||
|
||||
function reorderCoords(xs: ArrayLike<number>, index: ArrayLike<number>) {
|
||||
const ret = new Float32Array(xs.length);
|
||||
const _reorderBuffer = [0.123];
|
||||
function reorderCoordsInPlace(xs: number[], index: ArrayLike<number>) {
|
||||
const buffer = _reorderBuffer;
|
||||
|
||||
for (let i = 0, _i = xs.length; i < _i; i++) {
|
||||
ret[i] = xs[index[i]];
|
||||
buffer[i] = xs[index[i]];
|
||||
}
|
||||
for (let i = 0, _i = xs.length; i < _i; i++) {
|
||||
xs[i] = buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceOrderedCoords(xs: ArrayLike<number>, srcIndex: ArrayLike<number>, index: ArrayLike<number>) {
|
||||
const ret = new Float32Array(xs.length);
|
||||
|
||||
for (let i = 0, _i = xs.length; i < _i; i++) {
|
||||
ret[i] = xs[srcIndex[index[i]]];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function getAtomicConformationReordered(frame: Frame, atomId: Column<number>, srcIndex: ArrayLike<number>): AtomicConformation {
|
||||
return {
|
||||
id: UUID.create22(),
|
||||
atomId,
|
||||
occupancy: Column.ofConst(1, frame.elementCount, Column.Schema.int),
|
||||
B_iso_or_equiv: Column.ofConst(0, frame.elementCount, Column.Schema.float),
|
||||
xyzDefined: true,
|
||||
x: reorderCoords(frame.x, srcIndex),
|
||||
y: reorderCoords(frame.y, srcIndex),
|
||||
z: reorderCoords(frame.z, srcIndex)
|
||||
};
|
||||
function getOrderedCoords(xs: ArrayLike<number>, index: ArrayLike<number>) {
|
||||
const ret = new Float32Array(xs.length);
|
||||
|
||||
for (let i = 0, _i = xs.length; i < _i; i++) {
|
||||
ret[i] = xs[index[i]];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getInvertedCoords(xs: ArrayLike<number>, index: ArrayLike<number>) {
|
||||
const ret = new Float32Array(xs.length);
|
||||
|
||||
for (let i = 0, _i = xs.length; i < _i; i++) {
|
||||
ret[index[i]] = xs[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -105,9 +105,7 @@ export namespace Model {
|
||||
...model,
|
||||
id: UUID.create22(),
|
||||
modelNum: i,
|
||||
atomicConformation: isIdentity
|
||||
? Coordinates.getAtomicConformation(f, model.atomicConformation.atomId)
|
||||
: Coordinates.getAtomicConformationReordered(f, model.atomicConformation.atomId, srcIndexArray!),
|
||||
atomicConformation: Coordinates.getAtomicConformation(f, model.atomicConformation.atomId, srcIndexArray),
|
||||
// TODO: add support for supplying sphere and gaussian coordinates in addition to atomic coordinates?
|
||||
// coarseConformation: coarse.conformation,
|
||||
customProperties: new CustomProperties(),
|
||||
|
||||
@@ -620,9 +620,9 @@ export namespace BondType {
|
||||
'covalent': Flag.Covalent,
|
||||
'metal-coordination': Flag.MetallicCoordination,
|
||||
'hydrogen-bond': Flag.HydrogenBond,
|
||||
'disulfide': Flag.HydrogenBond,
|
||||
'aromatic': Flag.HydrogenBond,
|
||||
'computed': Flag.HydrogenBond,
|
||||
'disulfide': Flag.Disulfide,
|
||||
'aromatic': Flag.Aromatic,
|
||||
'computed': Flag.Computed,
|
||||
};
|
||||
export type Names = keyof typeof Names
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
|
||||
for (let li = 0; li < buCount; li++) {
|
||||
const lu = bondedUnits[li];
|
||||
const bUnit = structure.unitMap.get(lu.unitB) as Unit.Atomic;
|
||||
const bUnit = input.unitMap.get(lu.unitB) as Unit.Atomic;
|
||||
const bElements = bUnit.elements;
|
||||
const bonds = lu.getEdges(inputIndex);
|
||||
for (let bi = 0, _bi = bonds.length; bi < _bi; bi++) {
|
||||
|
||||
@@ -368,13 +368,16 @@ class Structure {
|
||||
|
||||
private _child: Structure | undefined;
|
||||
private _target: Structure | undefined;
|
||||
private _proxy: Structure | undefined;
|
||||
|
||||
/**
|
||||
* For `structure` with `parent` this returns a proxy that
|
||||
* targets `parent` and has `structure` attached as a child.
|
||||
*/
|
||||
asParent(): Structure {
|
||||
return this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
|
||||
if (this._proxy) return this._proxy;
|
||||
this._proxy = this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
|
||||
return this._proxy;
|
||||
}
|
||||
|
||||
get child(): Structure | undefined {
|
||||
|
||||
@@ -117,7 +117,6 @@ namespace UnitRing {
|
||||
// comes e.g. from `chem_comp_bond.pdbx_aromatic_flag`
|
||||
if (BondType.is(BondType.Flag.Aromatic, flags[j])) {
|
||||
if (SortedArray.has(ring, b[j])) aromaticBondCount += 1;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,9 @@ export const CubeProvider = DataFormatProvider({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
type DsCifParams = { entryId?: string | string[] };
|
||||
|
||||
export const DscifProvider = DataFormatProvider({
|
||||
label: 'DensityServer CIF',
|
||||
description: 'DensityServer CIF',
|
||||
@@ -200,7 +203,7 @@ export const DscifProvider = DataFormatProvider({
|
||||
isApplicable: (info, data) => {
|
||||
return guessCifVariant(info, data) === 'dscif' ? true : false;
|
||||
},
|
||||
parse: async (plugin, data, params?: Params) => {
|
||||
parse: async (plugin, data, params?: DsCifParams) => {
|
||||
const cifCell = await plugin.build().to(data).apply(StateTransforms.Data.ParseCif).commit();
|
||||
const b = plugin.build().to(cifCell);
|
||||
const blocks = cifCell.obj!.data.blocks.slice(1); // zero block contains query meta-data
|
||||
@@ -208,8 +211,11 @@ export const DscifProvider = DataFormatProvider({
|
||||
if (blocks.length !== 1 && blocks.length !== 2) throw new Error('unknown number of blocks');
|
||||
|
||||
const volumes: StateObjectSelector<PluginStateObject.Volume.Data>[] = [];
|
||||
let i = 0;
|
||||
for (const block of blocks) {
|
||||
volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header, entryId: params?.entryId }).selector);
|
||||
const entryId = Array.isArray(params?.entryId) ? params?.entryId[i] : params?.entryId;
|
||||
volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header, entryId }).selector);
|
||||
i++;
|
||||
}
|
||||
|
||||
await b.commit();
|
||||
|
||||
@@ -125,12 +125,13 @@ export namespace PluginStateObject {
|
||||
url: string | Asset.Url,
|
||||
isBinary: boolean,
|
||||
format: string,
|
||||
entryId?: string,
|
||||
entryId?: string | string[],
|
||||
isovalues: {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number
|
||||
alpha?: number,
|
||||
volumeIndex?: number,
|
||||
}[]
|
||||
}
|
||||
|
||||
|
||||
@@ -455,18 +455,20 @@ const LazyVolume = PluginStateTransform.BuiltIn({
|
||||
url: PD.Url(''),
|
||||
isBinary: PD.Boolean(false),
|
||||
format: PD.Text('ccp4'), // TODO: use Select based on available formats
|
||||
entryId: PD.Text(''),
|
||||
entryId: PD.Value<string | string[]>('', { isHidden: true }),
|
||||
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 })
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
volumeIndex: PD.Numeric(0),
|
||||
}, 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' });
|
||||
const entryId = Array.isArray(params.entryId) ? params.entryId.join(', ') : params.entryId;
|
||||
return new SO.Volume.Lazy(params, { label: `${entryId || params.url}`, description: 'Lazy Volume' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -205,19 +205,21 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
|
||||
try {
|
||||
const plugin = this.plugin;
|
||||
await plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { 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 firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const repr = plugin.build().to(volume);
|
||||
const repr = plugin.build();
|
||||
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 }
|
||||
}));
|
||||
repr
|
||||
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.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();
|
||||
|
||||
@@ -276,10 +276,12 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
|
||||
// TODO: is this correct behavior?
|
||||
if (!channel) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
const visible = b.data.repr.state.visible;
|
||||
const params = createVolumeProps(a.data, newParams.channel);
|
||||
const props = { ...b.data.repr.props, ...params.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
|
||||
await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
|
||||
b.data.repr.setState({ visible });
|
||||
b.data.sourceData = channel.data;
|
||||
|
||||
// TODO: set the transform here as well in case the structure moves?
|
||||
|
||||
@@ -118,6 +118,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
|
||||
updateState.createGeometry = true;
|
||||
}
|
||||
|
||||
if (currentStructure.child !== newStructure.child) {
|
||||
// console.log('new child');
|
||||
updateState.createGeometry = true;
|
||||
}
|
||||
|
||||
if (updateState.updateSize && !('uSize' in renderObject.values)) {
|
||||
updateState.createGeometry = true;
|
||||
}
|
||||
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -21,6 +21,7 @@ import { PuttyRepresentationProvider } from './representation/putty';
|
||||
import { SpacefillRepresentationProvider } from './representation/spacefill';
|
||||
import { LineRepresentationProvider } from './representation/line';
|
||||
import { GaussianVolumeRepresentationProvider } from './representation/gaussian-volume';
|
||||
import { BackboneRepresentationProvider } from './representation/backbone';
|
||||
|
||||
export class StructureRepresentationRegistry extends RepresentationRegistry<Structure, StructureRepresentationState> {
|
||||
constructor() {
|
||||
@@ -35,6 +36,7 @@ export class StructureRepresentationRegistry extends RepresentationRegistry<Stru
|
||||
export namespace StructureRepresentationRegistry {
|
||||
export const BuiltIn = {
|
||||
'cartoon': CartoonRepresentationProvider,
|
||||
'backbone': BackboneRepresentationProvider,
|
||||
'ball-and-stick': BallAndStickRepresentationProvider,
|
||||
'carbohydrate': CarbohydrateRepresentationProvider,
|
||||
'ellipsoid': EllipsoidRepresentationProvider,
|
||||
|
||||
@@ -1,29 +1,57 @@
|
||||
// /**
|
||||
// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
// *
|
||||
// * @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
// */
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
// import { PolymerBackboneVisual, PolymerBackboneParams } from '../visual/polymer-backbone-cylinder';
|
||||
// import { ParamDefinition as PD } from 'mol-util/param-definition';
|
||||
// import { UnitsRepresentation } from '../units-representation';
|
||||
// import { StructureRepresentation } from '../representation';
|
||||
// import { Representation } from 'mol-repr/representation';
|
||||
// import { ThemeRegistryContext } from 'mol-theme/theme';
|
||||
// import { Structure } from 'mol-model/structure';
|
||||
import { PolymerBackboneCylinderVisual, PolymerBackboneCylinderParams } from '../visual/polymer-backbone-cylinder';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { UnitsRepresentation } from '../units-representation';
|
||||
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
|
||||
import { ThemeRegistryContext } from '../../../mol-theme/theme';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { PolymerBackboneSphereParams, PolymerBackboneSphereVisual } from '../visual/polymer-backbone-sphere';
|
||||
import { PolymerGapParams, PolymerGapVisual } from '../visual/polymer-gap-cylinder';
|
||||
|
||||
// export const BackboneParams = {
|
||||
// ...PolymerBackboneParams,
|
||||
// }
|
||||
// export function getBackboneParams(ctx: ThemeRegistryContext, structure: Structure) {
|
||||
// return BackboneParams // TODO return copy
|
||||
// }
|
||||
// export type BackboneProps = PD.DefaultValues<typeof BackboneParams>
|
||||
const BackboneVisuals = {
|
||||
'polymer-backbone-cylinder': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerBackboneCylinderParams>) => UnitsRepresentation('Polymer backbone cylinder', ctx, getParams, PolymerBackboneCylinderVisual),
|
||||
'polymer-backbone-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerBackboneSphereParams>) => UnitsRepresentation('Polymer backbone sphere', ctx, getParams, PolymerBackboneSphereVisual),
|
||||
'polymer-gap': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerGapParams>) => UnitsRepresentation('Polymer gap cylinder', ctx, getParams, PolymerGapVisual),
|
||||
};
|
||||
|
||||
// export type BackboneRepresentation = StructureRepresentation<BackboneProps>
|
||||
export const BackboneParams = {
|
||||
...PolymerBackboneSphereParams,
|
||||
...PolymerBackboneCylinderParams,
|
||||
...PolymerGapParams,
|
||||
sizeAspectRatio: PD.Numeric(1, { min: 0.1, max: 3, step: 0.1 }),
|
||||
visuals: PD.MultiSelect(['polymer-backbone-cylinder', 'polymer-backbone-sphere', 'polymer-gap'], PD.objectToOptions(BackboneVisuals))
|
||||
};
|
||||
export type BackboneParams = typeof BackboneParams
|
||||
export function getBackboneParams(ctx: ThemeRegistryContext, structure: Structure) {
|
||||
const params = PD.clone(BackboneParams);
|
||||
let hasGaps = false;
|
||||
structure.units.forEach(u => {
|
||||
if (!hasGaps && u.gapElements.length) hasGaps = true;
|
||||
});
|
||||
params.visuals.defaultValue = ['polymer-backbone-cylinder', 'polymer-backbone-sphere'];
|
||||
if (hasGaps) params.visuals.defaultValue.push('polymer-gap');
|
||||
return params;
|
||||
}
|
||||
|
||||
// export function BackboneRepresentation(defaultProps: BackboneProps): BackboneRepresentation {
|
||||
// return Representation.createMulti('Backbone', defaultProps, [
|
||||
// UnitsRepresentation('Polymer backbone cylinder', defaultProps, PolymerBackboneVisual)
|
||||
// ])
|
||||
// }
|
||||
export type BackboneRepresentation = StructureRepresentation<BackboneParams>
|
||||
export function BackboneRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, BackboneParams>): BackboneRepresentation {
|
||||
return Representation.createMulti('Backbone', ctx, getParams, StructureRepresentationStateBuilder, BackboneVisuals as unknown as Representation.Def<Structure, BackboneParams>);
|
||||
}
|
||||
|
||||
export const BackboneRepresentationProvider = StructureRepresentationProvider({
|
||||
name: 'backbone',
|
||||
label: 'Backbone',
|
||||
description: 'Displays polymer backbone with cylinders and spheres.',
|
||||
factory: BackboneRepresentation,
|
||||
getParams: getBackboneParams,
|
||||
defaultValues: PD.getDefaultValues(BackboneParams),
|
||||
defaultColorTheme: { name: 'chain-id' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.polymerResidueCount > 0,
|
||||
});
|
||||
@@ -66,7 +66,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
visuals.set(group.hashCode, { visual, group });
|
||||
if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
|
||||
}
|
||||
} else if (structure && !Structure.areUnitIdsAndIndicesEqual(structure, _structure)) {
|
||||
} else if (structure && (!Structure.areUnitIdsAndIndicesEqual(structure, _structure) || structure.child !== _structure.child)) {
|
||||
// console.log(label, 'structures not equivalent');
|
||||
// Tries to re-use existing visuals for the groups of the new structure.
|
||||
// Creates additional visuals if needed, destroys left-over visuals.
|
||||
|
||||
@@ -120,6 +120,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
|
||||
updateState.updateColor = true;
|
||||
}
|
||||
|
||||
if (currentStructureGroup.structure.child !== newStructureGroup.structure.child) {
|
||||
// console.log('new child');
|
||||
updateState.createGeometry = true;
|
||||
}
|
||||
|
||||
if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) {
|
||||
// console.log('new unitKinds');
|
||||
updateState.createGeometry = true;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// TODO
|
||||
@@ -11,7 +11,7 @@ import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { BitFlags, arrayEqual } from '../../../mol-util';
|
||||
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkStyle } from './util/link';
|
||||
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps, LinkStyle } from './util/link';
|
||||
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual, ComplexCylindersParams, ComplexCylindersVisual } from '../complex-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BondType } from '../../../mol-model/structure/model/types';
|
||||
@@ -34,22 +34,20 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
|
||||
|
||||
const tmpRef = Vec3();
|
||||
|
||||
function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>) {
|
||||
function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>): LinkBuilderProps {
|
||||
const locE = StructureElement.Location.create(structure);
|
||||
const locB = Bond.Location(structure, undefined, undefined, structure, undefined, undefined);
|
||||
|
||||
const bonds = structure.interUnitBonds;
|
||||
const { edgeCount, edges } = bonds;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength } = props;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds } = props;
|
||||
|
||||
const delta = Vec3();
|
||||
|
||||
let stub: undefined | ((edgeIndex: number) => boolean);
|
||||
|
||||
if (props.includeParent) {
|
||||
const { child } = structure;
|
||||
if (!child) throw new Error('expected child to exist');
|
||||
|
||||
const { child } = structure;
|
||||
if (props.includeParent && child) {
|
||||
stub = (edgeIndex: number) => {
|
||||
const b = edges[edgeIndex];
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
@@ -137,13 +135,15 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
|
||||
if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
} else {
|
||||
return LinkStyle.Solid;
|
||||
} else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
|
||||
return LinkStyle.Aromatic;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
}
|
||||
|
||||
return LinkStyle.Solid;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
return radius(edgeIndex) * sizeAspectRatio;
|
||||
@@ -205,6 +205,7 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
|
||||
eachLocation: eachInterBond,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Structure, StructureElement, Bond, Unit } from '../../../mol-model/stru
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { BitFlags, arrayEqual } from '../../../mol-util';
|
||||
import { LinkStyle, createLinkLines } from './util/link';
|
||||
import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
|
||||
import { ComplexVisual, ComplexLinesVisual, ComplexLinesParams } from '../complex-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BondType } from '../../../mol-model/structure/model/types';
|
||||
@@ -32,14 +32,14 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
|
||||
function createInterUnitBondLines(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondLineParams>, lines?: Lines) {
|
||||
const bonds = structure.interUnitBonds;
|
||||
const { edgeCount, edges } = bonds;
|
||||
const { sizeFactor } = props;
|
||||
const { sizeFactor, aromaticBonds } = props;
|
||||
|
||||
if (!edgeCount) return Lines.createEmpty(lines);
|
||||
|
||||
const ref = Vec3();
|
||||
const loc = StructureElement.Location.create();
|
||||
|
||||
const builderProps = {
|
||||
const builderProps: LinkBuilderProps = {
|
||||
linkCount: edgeCount,
|
||||
referencePosition: (edgeIndex: number) => {
|
||||
const b = edges[edgeIndex];
|
||||
@@ -73,13 +73,15 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
|
||||
if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
} else {
|
||||
return LinkStyle.Solid;
|
||||
}else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
|
||||
return LinkStyle.Aromatic;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
}
|
||||
|
||||
return LinkStyle.Solid;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
const b = edges[edgeIndex];
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { arrayEqual } from '../../../mol-util';
|
||||
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkStyle } from './util/link';
|
||||
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps, LinkStyle } from './util/link';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup, UnitsCylindersParams, UnitsCylindersVisual } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BondType } from '../../../mol-model/structure/model/types';
|
||||
@@ -22,16 +22,17 @@ import { IntAdjacencyGraph } from '../../../mol-math/graph';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
|
||||
import { SortedArray } from '../../../mol-data/int';
|
||||
import { arrayIntersectionSize } from '../../../mol-util/array';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const isBondType = BondType.is;
|
||||
|
||||
function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>) {
|
||||
function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>): LinkBuilderProps {
|
||||
const elements = unit.elements;
|
||||
const bonds = unit.bonds;
|
||||
const { edgeCount, a, b, edgeProps, offset } = bonds;
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength } = props;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds } = props;
|
||||
|
||||
const vRef = Vec3(), delta = Vec3();
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
@@ -41,9 +42,8 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
const locE = StructureElement.Location.create(structure, unit);
|
||||
const locB = Bond.Location(structure, unit, undefined, structure, unit, undefined);
|
||||
|
||||
if (props.includeParent) {
|
||||
const { child } = structure;
|
||||
if (!child) throw new Error('expected child to exist');
|
||||
const { child } = structure;
|
||||
if (props.includeParent && child) {
|
||||
const childUnit = child.unitMap.get(unit.id);
|
||||
if (!childUnit) throw new Error('expected childUnit to exist');
|
||||
|
||||
@@ -70,6 +70,8 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
return theme.size.size(locE) * sizeFactor;
|
||||
};
|
||||
|
||||
const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
|
||||
|
||||
return {
|
||||
linkCount: edgeCount * 2,
|
||||
referencePosition: (edgeIndex: number) => {
|
||||
@@ -77,17 +79,28 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
|
||||
if (aI > bI) [aI, bI] = [bI, aI];
|
||||
if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
|
||||
// TODO prefer reference atoms within rings
|
||||
|
||||
const aR = elementRingIndices.get(aI);
|
||||
let maxSize = 0;
|
||||
|
||||
for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) {
|
||||
const _bI = b[i];
|
||||
if (_bI !== bI && _bI !== aI) return pos(elements[_bI], vRef);
|
||||
if (_bI !== bI && _bI !== aI) {
|
||||
if (aR) {
|
||||
const _bR = elementRingIndices.get(_bI);
|
||||
if (!_bR) continue;
|
||||
|
||||
const size = arrayIntersectionSize(aR, _bR);
|
||||
if (size > maxSize) {
|
||||
maxSize = size;
|
||||
pos(elements[_bI], vRef);
|
||||
}
|
||||
} else {
|
||||
return pos(elements[_bI], vRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) {
|
||||
const _aI = a[i];
|
||||
if (_aI !== aI && _aI !== bI) return pos(elements[_aI], vRef);
|
||||
}
|
||||
return null;
|
||||
return maxSize > 0 ? vRef : null;
|
||||
},
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
pos(elements[a[edgeIndex]], posA);
|
||||
@@ -111,13 +124,24 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
if (isBondType(f, BondType.Flag.MetallicCoordination) || isBondType(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
} else {
|
||||
return LinkStyle.Solid;
|
||||
} else if (aromaticBonds) {
|
||||
const aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
const aR = elementAromaticRingIndices.get(aI);
|
||||
const bR = elementAromaticRingIndices.get(bI);
|
||||
const arCount = (aR && bR) ? arrayIntersectionSize(aR, bR) : 0;
|
||||
|
||||
if (arCount || isBondType(f, BondType.Flag.Aromatic)) {
|
||||
if (arCount === 2) {
|
||||
return LinkStyle.MirroredAromatic;
|
||||
} else {
|
||||
return LinkStyle.Aromatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return o === 2 ? LinkStyle.Double : LinkStyle.Solid;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
return radius(edgeIndex) * sizeAspectRatio;
|
||||
@@ -187,6 +211,7 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
|
||||
eachLocation: eachIntraBond,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IntraUnitBondCylinderParams>, currentProps: PD.Values<IntraUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
@@ -197,7 +222,8 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
|
||||
newProps.stubCap !== currentProps.stubCap ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds
|
||||
);
|
||||
|
||||
const newUnit = newStructureGroup.group.units[0];
|
||||
@@ -239,7 +265,8 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
|
||||
newProps.stubCap !== currentProps.stubCap ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds
|
||||
);
|
||||
|
||||
const newUnit = newStructureGroup.group.units[0];
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Unit, Structure, StructureElement } from '../../../mol-model/structure'
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { arrayEqual } from '../../../mol-util';
|
||||
import { LinkStyle, createLinkLines } from './util/link';
|
||||
import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
|
||||
import { UnitsVisual, UnitsLinesParams, UnitsLinesVisual, StructureGroup } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BondType } from '../../../mol-model/structure/model/types';
|
||||
@@ -18,6 +18,7 @@ import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntr
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Lines } from '../../../mol-geo/geometry/lines/lines';
|
||||
import { IntAdjacencyGraph } from '../../../mol-math/graph';
|
||||
import { arrayIntersectionSize } from '../../../mol-util/array';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const isBondType = BondType.is;
|
||||
@@ -29,41 +30,50 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
const childUnit = child?.unitMap.get(unit.id);
|
||||
if (child && !childUnit) return Lines.createEmpty(lines);
|
||||
|
||||
if (props.includeParent) {
|
||||
if (!child) throw new Error('expected child to exist');
|
||||
}
|
||||
|
||||
const location = StructureElement.Location.create(structure, unit);
|
||||
|
||||
const elements = unit.elements;
|
||||
const bonds = unit.bonds;
|
||||
const { edgeCount, a, b, edgeProps, offset } = bonds;
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor } = props;
|
||||
const { sizeFactor, aromaticBonds } = props;
|
||||
|
||||
if (!edgeCount) return Lines.createEmpty(lines);
|
||||
|
||||
const vRef = Vec3();
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
|
||||
const builderProps = {
|
||||
const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
|
||||
|
||||
const builderProps: LinkBuilderProps = {
|
||||
linkCount: edgeCount * 2,
|
||||
referencePosition: (edgeIndex: number) => {
|
||||
let aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
|
||||
if (aI > bI) [aI, bI] = [bI, aI];
|
||||
if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
|
||||
// TODO prefer reference atoms within rings
|
||||
|
||||
const aR = elementRingIndices.get(aI);
|
||||
let maxSize = 0;
|
||||
|
||||
for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) {
|
||||
const _bI = b[i];
|
||||
if (_bI !== bI && _bI !== aI) return pos(elements[_bI], vRef);
|
||||
if (_bI !== bI && _bI !== aI) {
|
||||
if (aR) {
|
||||
const _bR = elementRingIndices.get(_bI);
|
||||
if (!_bR) continue;
|
||||
|
||||
const size = arrayIntersectionSize(aR, _bR);
|
||||
if (size > maxSize) {
|
||||
maxSize = size;
|
||||
pos(elements[_bI], vRef);
|
||||
}
|
||||
} else {
|
||||
return pos(elements[_bI], vRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) {
|
||||
const _aI = a[i];
|
||||
if (_aI !== aI && _aI !== bI) return pos(elements[_aI], vRef);
|
||||
}
|
||||
return null;
|
||||
return maxSize > 0 ? vRef : null;
|
||||
},
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
pos(elements[a[edgeIndex]], posA);
|
||||
@@ -75,13 +85,24 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
if (isBondType(f, BondType.Flag.MetallicCoordination) || isBondType(f, BondType.Flag.HydrogenBond)) {
|
||||
// show metall coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
} else {
|
||||
return LinkStyle.Solid;
|
||||
} else if (aromaticBonds) {
|
||||
const aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
const aR = elementAromaticRingIndices.get(aI);
|
||||
const bR = elementAromaticRingIndices.get(bI);
|
||||
const arCount = (aR && bR) ? arrayIntersectionSize(aR, bR) : 0;
|
||||
|
||||
if (arCount || isBondType(f, BondType.Flag.Aromatic)) {
|
||||
if (arCount === 2) {
|
||||
return LinkStyle.MirroredAromatic;
|
||||
} else {
|
||||
return LinkStyle.Aromatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return o === 2 ? LinkStyle.Double : LinkStyle.Solid;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
location.element = elements[a[edgeIndex]];
|
||||
@@ -123,7 +144,8 @@ export function IntraUnitBondLineVisual(materialId: number): UnitsVisual<IntraUn
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds
|
||||
);
|
||||
|
||||
const newUnit = newStructureGroup.group.units[0];
|
||||
|
||||
@@ -1,34 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure } from '../../../mol-model/structure';
|
||||
import { Unit, Structure, StructureElement, ElementIndex } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { CylinderProps } from '../../../mol-geo/primitive/cylinder';
|
||||
import { PolymerBackboneIterator } from './util/polymer';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { eachPolymerElement, getPolymerElementLoci, NucleicShift, PolymerLocationIterator, StandardShift } from './util/polymer';
|
||||
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { ElementIterator, getElementLoci, eachElement } from './util/element';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsCylindersVisual, UnitsCylindersParams, StructureGroup } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { isNucleic, MoleculeType } from '../../../mol-model/structure/model/types';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
|
||||
import { CylindersBuilder } from '../../../mol-geo/geometry/cylinders/cylinders-builder';
|
||||
import { eachPolymerBackboneLink } from './util/polymer/backbone';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3scale = Vec3.scale;
|
||||
const v3add = Vec3.add;
|
||||
const v3sub = Vec3.sub;
|
||||
|
||||
export const PolymerBackboneCylinderParams = {
|
||||
...UnitsMeshParams,
|
||||
...UnitsCylindersParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
|
||||
tryUseImpostor: PD.Boolean(true),
|
||||
};
|
||||
export const DefaultPolymerBackboneCylinderProps = PD.getDefaultValues(PolymerBackboneCylinderParams);
|
||||
export type PolymerBackboneCylinderProps = typeof DefaultPolymerBackboneCylinderProps
|
||||
export type PolymerBackboneCylinderParams = typeof PolymerBackboneCylinderParams
|
||||
|
||||
export function PolymerBackboneCylinderVisual(materialId: number, structure: Structure, props: PD.Values<PolymerBackboneCylinderParams>, webgl?: WebGLContext) {
|
||||
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
|
||||
? PolymerBackboneCylinderImpostorVisual(materialId)
|
||||
: PolymerBackboneCylinderMeshVisual(materialId);
|
||||
}
|
||||
|
||||
interface PolymerBackboneCylinderProps {
|
||||
radialSegments: number,
|
||||
sizeFactor: number,
|
||||
}
|
||||
|
||||
function createPolymerBackboneCylinderImpostor(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneCylinderProps, cylinders?: Cylinders) {
|
||||
const polymerElementCount = unit.polymerElements.length;
|
||||
if (!polymerElementCount) return Cylinders.createEmpty(cylinders);
|
||||
|
||||
const cylindersCountEstimate = polymerElementCount * 2;
|
||||
const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
|
||||
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
const pA = Vec3();
|
||||
const pB = Vec3();
|
||||
const pM = Vec3();
|
||||
|
||||
const add = function(indexA: ElementIndex, indexB: ElementIndex, groupA: number, groupB: number, moleculeType: MoleculeType) {
|
||||
pos(indexA, pA);
|
||||
pos(indexB, pB);
|
||||
|
||||
const isNucleicType = isNucleic(moleculeType);
|
||||
const shift = isNucleicType ? NucleicShift : StandardShift;
|
||||
|
||||
v3add(pM, pA, v3scale(pM, v3sub(pM, pB, pA), shift));
|
||||
builder.add(pA[0], pA[1], pA[2], pM[0], pM[1], pM[2], 1, false, false, groupA);
|
||||
builder.add(pM[0], pM[1], pM[2], pB[0], pB[1], pB[2], 1, false, false, groupB);
|
||||
};
|
||||
|
||||
eachPolymerBackboneLink(unit, add);
|
||||
|
||||
const c = builder.getCylinders();
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
|
||||
c.setBoundingSphere(sphere);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
export function PolymerBackboneCylinderImpostorVisual(materialId: number): UnitsVisual<PolymerBackboneCylinderParams> {
|
||||
return UnitsCylindersVisual<PolymerBackboneCylinderParams>({
|
||||
defaultProps: PD.getDefaultValues(PolymerBackboneCylinderParams),
|
||||
createGeometry: createPolymerBackboneCylinderImpostor,
|
||||
createLocationIterator: PolymerLocationIterator.fromGroup,
|
||||
getLoci: getPolymerElementLoci,
|
||||
eachLocation: eachPolymerElement,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneCylinderParams>, currentProps: PD.Values<PolymerBackboneCylinderParams>) => { },
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneCylinderParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseImpostor || !webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
// TODO do group id based on polymer index not element index
|
||||
function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneCylinderProps, mesh?: Mesh) {
|
||||
const polymerElementCount = unit.polymerElements.length;
|
||||
if (!polymerElementCount) return Mesh.createEmpty(mesh);
|
||||
@@ -38,26 +106,34 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
|
||||
const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2;
|
||||
const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 10, mesh);
|
||||
|
||||
const { elements } = unit;
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
const pA = Vec3.zero();
|
||||
const pB = Vec3.zero();
|
||||
const pA = Vec3();
|
||||
const pB = Vec3();
|
||||
const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments };
|
||||
|
||||
const polymerBackboneIt = PolymerBackboneIterator(structure, unit);
|
||||
while (polymerBackboneIt.hasNext) {
|
||||
const { centerA, centerB } = polymerBackboneIt.move();
|
||||
const centerA = StructureElement.Location.create(structure, unit);
|
||||
const centerB = StructureElement.Location.create(structure, unit);
|
||||
|
||||
const add = function(indexA: ElementIndex, indexB: ElementIndex, groupA: number, groupB: number, moleculeType: MoleculeType) {
|
||||
centerA.element = indexA;
|
||||
centerB.element = indexB;
|
||||
|
||||
pos(centerA.element, pA);
|
||||
pos(centerB.element, pB);
|
||||
|
||||
const isNucleicType = isNucleic(moleculeType);
|
||||
const shift = isNucleicType ? NucleicShift : StandardShift;
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) * sizeFactor;
|
||||
builderState.currentGroup = OrderedSet.indexOf(elements, centerA.element);
|
||||
addCylinder(builderState, pA, pB, 0.5, cylinderProps);
|
||||
builderState.currentGroup = groupA;
|
||||
addCylinder(builderState, pA, pB, shift, cylinderProps);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) * sizeFactor;
|
||||
builderState.currentGroup = OrderedSet.indexOf(elements, centerB.element);
|
||||
addCylinder(builderState, pB, pA, 0.5, cylinderProps);
|
||||
}
|
||||
builderState.currentGroup = groupB;
|
||||
addCylinder(builderState, pB, pA, 1 - shift, cylinderProps);
|
||||
};
|
||||
|
||||
eachPolymerBackboneLink(unit, add);
|
||||
|
||||
const m = MeshBuilder.getMesh(builderState);
|
||||
|
||||
@@ -67,25 +143,21 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
|
||||
return m;
|
||||
}
|
||||
|
||||
export const PolymerBackboneParams = {
|
||||
...UnitsMeshParams,
|
||||
...PolymerBackboneCylinderParams,
|
||||
};
|
||||
export type PolymerBackboneParams = typeof PolymerBackboneParams
|
||||
|
||||
export function PolymerBackboneVisual(materialId: number): UnitsVisual<PolymerBackboneParams> {
|
||||
return UnitsMeshVisual<PolymerBackboneParams>({
|
||||
defaultProps: PD.getDefaultValues(PolymerBackboneParams),
|
||||
export function PolymerBackboneCylinderMeshVisual(materialId: number): UnitsVisual<PolymerBackboneCylinderParams> {
|
||||
return UnitsMeshVisual<PolymerBackboneCylinderParams>({
|
||||
defaultProps: PD.getDefaultValues(PolymerBackboneCylinderParams),
|
||||
createGeometry: createPolymerBackboneCylinderMesh,
|
||||
// TODO create a specialized location iterator
|
||||
createLocationIterator: ElementIterator.fromGroup,
|
||||
getLoci: getElementLoci,
|
||||
eachLocation: eachElement,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneParams>, currentProps: PD.Values<PolymerBackboneParams>) => {
|
||||
createLocationIterator: PolymerLocationIterator.fromGroup,
|
||||
getLoci: getPolymerElementLoci,
|
||||
eachLocation: eachPolymerElement,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneCylinderParams>, currentProps: PD.Values<PolymerBackboneCylinderParams>) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.radialSegments !== currentProps.radialSegments
|
||||
);
|
||||
},
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneCylinderParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseImpostor && !!webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -1 +1,131 @@
|
||||
// TODO
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure, ElementIndex, StructureElement } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { eachPolymerElement, getPolymerElementLoci, PolymerLocationIterator } from './util/polymer';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsSpheresVisual, UnitsSpheresParams, StructureGroup } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { sphereVertexCount } from '../../../mol-geo/primitive/sphere';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
|
||||
import { SpheresBuilder } from '../../../mol-geo/geometry/spheres/spheres-builder';
|
||||
import { eachPolymerBackboneElement } from './util/polymer/backbone';
|
||||
|
||||
export const PolymerBackboneSphereParams = {
|
||||
...UnitsMeshParams,
|
||||
...UnitsSpheresParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
tryUseImpostor: PD.Boolean(true),
|
||||
};
|
||||
export type PolymerBackboneSphereParams = typeof PolymerBackboneSphereParams
|
||||
|
||||
export function PolymerBackboneSphereVisual(materialId: number, structure: Structure, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) {
|
||||
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
|
||||
? PolymerBackboneSphereImpostorVisual(materialId)
|
||||
: PolymerBackboneSphereMeshVisual(materialId);
|
||||
}
|
||||
|
||||
interface PolymerBackboneSphereProps {
|
||||
detail: number,
|
||||
sizeFactor: number,
|
||||
}
|
||||
|
||||
function createPolymerBackboneSphereImpostor(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneSphereProps, spheres?: Spheres) {
|
||||
const polymerElementCount = unit.polymerElements.length;
|
||||
if (!polymerElementCount) return Spheres.createEmpty(spheres);
|
||||
|
||||
const builder = SpheresBuilder.create(polymerElementCount, polymerElementCount / 2, spheres);
|
||||
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
const p = Vec3();
|
||||
|
||||
const add = (index: ElementIndex, group: number) => {
|
||||
pos(index, p);
|
||||
builder.add(p[0], p[1], p[2], group);
|
||||
};
|
||||
|
||||
eachPolymerBackboneElement(unit, add);
|
||||
|
||||
const s = builder.getSpheres();
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
|
||||
s.setBoundingSphere(sphere);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
export function PolymerBackboneSphereImpostorVisual(materialId: number): UnitsVisual<PolymerBackboneSphereParams> {
|
||||
return UnitsSpheresVisual<PolymerBackboneSphereParams>({
|
||||
defaultProps: PD.getDefaultValues(PolymerBackboneSphereParams),
|
||||
createGeometry: createPolymerBackboneSphereImpostor,
|
||||
createLocationIterator: PolymerLocationIterator.fromGroup,
|
||||
getLoci: getPolymerElementLoci,
|
||||
eachLocation: eachPolymerElement,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneSphereParams>, currentProps: PD.Values<PolymerBackboneSphereParams>) => { },
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseImpostor || !webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
function createPolymerBackboneSphereMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneSphereProps, mesh?: Mesh) {
|
||||
const polymerElementCount = unit.polymerElements.length;
|
||||
if (!polymerElementCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { detail, sizeFactor } = props;
|
||||
|
||||
const vertexCount = polymerElementCount * sphereVertexCount(detail);
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh);
|
||||
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
const p = Vec3();
|
||||
const center = StructureElement.Location.create(structure, unit);
|
||||
|
||||
const add = (index: ElementIndex, group: number) => {
|
||||
center.element = index;
|
||||
pos(center.element, p);
|
||||
builderState.currentGroup = group;
|
||||
addSphere(builderState, p, theme.size.size(center) * sizeFactor, detail);
|
||||
};
|
||||
|
||||
eachPolymerBackboneElement(unit, add);
|
||||
|
||||
const m = MeshBuilder.getMesh(builderState);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
export function PolymerBackboneSphereMeshVisual(materialId: number): UnitsVisual<PolymerBackboneSphereParams> {
|
||||
return UnitsMeshVisual<PolymerBackboneSphereParams>({
|
||||
defaultProps: PD.getDefaultValues(PolymerBackboneSphereParams),
|
||||
createGeometry: createPolymerBackboneSphereMesh,
|
||||
createLocationIterator: PolymerLocationIterator.fromGroup,
|
||||
getLoci: getPolymerElementLoci,
|
||||
eachLocation: eachPolymerElement,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneSphereParams>, currentProps: PD.Values<PolymerBackboneSphereParams>) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.detail !== currentProps.detail
|
||||
);
|
||||
},
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseImpostor && !!webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export const BondParams = {
|
||||
includeTypes: PD.MultiSelect(ObjectKeys(BondType.Names), PD.objectToOptions(BondType.Names)),
|
||||
excludeTypes: PD.MultiSelect([] as BondType.Names[], PD.objectToOptions(BondType.Names)),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
aromaticBonds: PD.Boolean(false, { description: 'Display aromatic bonds with dashes' }),
|
||||
};
|
||||
export const DefaultBondProps = PD.getDefaultValues(BondParams);
|
||||
export type BondProps = typeof DefaultBondProps
|
||||
|
||||
@@ -46,7 +46,7 @@ export function makeElementIgnoreTest(structure: Structure, unit: Unit, props: E
|
||||
const childUnit = child?.unitMap.get(unit.id);
|
||||
if (child && !childUnit) throw new Error('expected childUnit to exist if child exists');
|
||||
|
||||
if (!child && ((!ignoreHydrogens && !traceOnly) || traceOnly)) return;
|
||||
if (!child && !ignoreHydrogens && !traceOnly) return;
|
||||
|
||||
return (element: ElementIndex) => {
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Cylinders } from '../../../../mol-geo/geometry/cylinders/cylinders';
|
||||
import { CylindersBuilder } from '../../../../mol-geo/geometry/cylinders/cylinders-builder';
|
||||
|
||||
export const LinkCylinderParams = {
|
||||
linkScale: PD.Numeric(0.4, { min: 0, max: 1, step: 0.1 }),
|
||||
linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
|
||||
linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
|
||||
linkCap: PD.Boolean(false),
|
||||
dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
|
||||
@@ -84,7 +84,9 @@ export const enum LinkStyle {
|
||||
Dashed = 1,
|
||||
Double = 2,
|
||||
Triple = 3,
|
||||
Disk = 4
|
||||
Disk = 4,
|
||||
Aromatic = 5,
|
||||
MirroredAromatic = 6,
|
||||
}
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
@@ -133,6 +135,8 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
const [topCap, bottomCap] = (v3dot(tmpV12, up) > 0) ? [linkStub, linkCap] : [linkCap, linkStub];
|
||||
builderState.currentGroup = edgeIndex;
|
||||
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
|
||||
if (linkStyle === LinkStyle.Solid) {
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
cylinderProps.topCap = topCap;
|
||||
@@ -144,20 +148,41 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
|
||||
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 : 3;
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 :
|
||||
linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
||||
const multiRadius = linkRadius * (linkScale / (0.5 * order));
|
||||
const absOffset = (linkRadius - multiRadius) * linkSpacing;
|
||||
|
||||
calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null);
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
|
||||
cylinderProps.topCap = topCap;
|
||||
cylinderProps.bottomCap = bottomCap;
|
||||
|
||||
if (order === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps);
|
||||
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * linkScale;
|
||||
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
|
||||
|
||||
if (linkStyle === LinkStyle.MirroredAromatic) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
|
||||
}
|
||||
} else {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
|
||||
if (order === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.Disk) {
|
||||
v3scale(tmpV12, tmpV12, 0.475);
|
||||
v3add(va, va, tmpV12);
|
||||
@@ -190,12 +215,17 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
|
||||
const va = Vec3();
|
||||
const vb = Vec3();
|
||||
const vm = Vec3();
|
||||
const vShift = Vec3();
|
||||
|
||||
// automatically adjust length for evenly spaced dashed cylinders
|
||||
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
|
||||
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
|
||||
|
||||
const aromaticSegmentCount = 3;
|
||||
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
|
||||
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
||||
if (ignore && ignore(edgeIndex)) continue;
|
||||
|
||||
@@ -206,24 +236,45 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
const linkStub = stubCap && (stub ? stub(edgeIndex) : false);
|
||||
|
||||
if (linkStyle === LinkStyle.Solid) {
|
||||
v3scale(vb, v3add(vb, va, vb), 0.5);
|
||||
builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], 1, linkCap, linkStub, edgeIndex);
|
||||
v3scale(vm, v3add(vm, va, vb), 0.5);
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Dashed) {
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
|
||||
v3scale(vb, v3add(vb, va, vb), 0.5);
|
||||
const order = linkStyle === LinkStyle.Double ? 2 : 3;
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 :
|
||||
linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
||||
const multiScale = linkScale / (0.5 * order);
|
||||
const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
|
||||
|
||||
v3scale(vm, v3add(vm, va, vb), 0.5);
|
||||
calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null);
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], multiScale, linkCap, false, edgeIndex);
|
||||
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vb[0] + vShift[0], vb[1] + vShift[1], vb[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vb[0] - vShift[0], vb[1] - vShift[1], vb[2] - vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
|
||||
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
|
||||
|
||||
if (linkStyle === LinkStyle.MirroredAromatic) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
|
||||
}
|
||||
} else {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], multiScale, linkCap, false, edgeIndex);
|
||||
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.Disk) {
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), 0.475);
|
||||
v3add(va, va, tmpV12);
|
||||
@@ -252,12 +303,17 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
|
||||
const va = Vec3();
|
||||
const vb = Vec3();
|
||||
const vm = Vec3();
|
||||
const vShift = Vec3();
|
||||
|
||||
// automatically adjust length for evenly spaced dashed lines
|
||||
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
|
||||
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
|
||||
|
||||
const aromaticSegmentCount = 3;
|
||||
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
|
||||
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
||||
if (ignore && ignore(edgeIndex)) continue;
|
||||
|
||||
@@ -266,24 +322,45 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
const linkStyle = style ? style(edgeIndex) : LinkStyle.Solid;
|
||||
|
||||
if (linkStyle === LinkStyle.Solid) {
|
||||
v3scale(vb, v3add(vb, va, vb), 0.5);
|
||||
builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], edgeIndex);
|
||||
v3scale(vm, v3add(vm, va, vb), 0.5);
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Dashed) {
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
builder.addFixedCountDashes(va, vb, segmentCount, edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
|
||||
v3scale(vb, v3add(vb, va, vb), 0.5);
|
||||
const order = linkStyle === LinkStyle.Double ? 2 : 3;
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 :
|
||||
linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
||||
const multiRadius = 1 * (linkScale / (0.5 * order));
|
||||
const absOffset = (1 - multiRadius) * linkSpacing;
|
||||
|
||||
v3scale(vm, v3add(vm, va, vb), 0.5);
|
||||
calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null);
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], edgeIndex);
|
||||
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vb[0] + vShift[0], vb[1] + vShift[1], vb[2] + vShift[2], edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vb[0] - vShift[0], vb[1] - vShift[1], vb[2] - vShift[2], edgeIndex);
|
||||
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
||||
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, edgeIndex);
|
||||
|
||||
if (linkStyle === LinkStyle.MirroredAromatic) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, edgeIndex);
|
||||
}
|
||||
} else {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
||||
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], edgeIndex);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.Disk) {
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), 0.475);
|
||||
v3add(va, va, tmpV12);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { PickingId } from '../../../../mol-geo/geometry/picking';
|
||||
import { StructureGroup } from '../../../structure/units-visual';
|
||||
import { getResidueLoci } from './common';
|
||||
|
||||
export * from './polymer/backbone-iterator';
|
||||
export * from './polymer/backbone';
|
||||
export * from './polymer/gap-iterator';
|
||||
export * from './polymer/trace-iterator';
|
||||
export * from './polymer/curve-segment';
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Unit, Structure, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure';
|
||||
import { Segmentation } from '../../../../../mol-data/int';
|
||||
import { Iterator } from '../../../../../mol-data/iterator';
|
||||
import { SortedRanges } from '../../../../../mol-data/int/sorted-ranges';
|
||||
import { getPolymerRanges } from '../polymer';
|
||||
|
||||
/** Iterates over consecutive pairs of residues/coarse elements in polymers */
|
||||
export function PolymerBackboneIterator(structure: Structure, unit: Unit): Iterator<PolymerBackbonePair> {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(structure, unit);
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return new CoarsePolymerBackboneIterator(structure, unit);
|
||||
}
|
||||
}
|
||||
|
||||
interface PolymerBackbonePair {
|
||||
centerA: StructureElement.Location
|
||||
centerB: StructureElement.Location
|
||||
}
|
||||
|
||||
function createPolymerBackbonePair (structure: Structure, unit: Unit) {
|
||||
return {
|
||||
centerA: StructureElement.Location.create(structure, unit),
|
||||
centerB: StructureElement.Location.create(structure, unit),
|
||||
};
|
||||
}
|
||||
|
||||
const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue, cycle }
|
||||
|
||||
export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
|
||||
private traceElementIndex: ArrayLike<ElementIndex>
|
||||
private value: PolymerBackbonePair
|
||||
private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
|
||||
private residueIt: Segmentation.SegmentIterator<ResidueIndex>
|
||||
private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer
|
||||
private residueSegment: Segmentation.Segment<ResidueIndex>
|
||||
hasNext: boolean = false;
|
||||
|
||||
move() {
|
||||
if (this.state === AtomicPolymerBackboneIteratorState.nextPolymer) {
|
||||
while (this.polymerIt.hasNext) {
|
||||
this.residueIt.setSegment(this.polymerIt.move());
|
||||
if (this.residueIt.hasNext) {
|
||||
this.residueSegment = this.residueIt.move();
|
||||
this.value.centerB.element = this.traceElementIndex[this.residueSegment.index];
|
||||
this.state = AtomicPolymerBackboneIteratorState.nextResidue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state === AtomicPolymerBackboneIteratorState.nextResidue) {
|
||||
this.residueSegment = this.residueIt.move();
|
||||
this.value.centerA.element = this.value.centerB.element;
|
||||
this.value.centerB.element = this.traceElementIndex[this.residueSegment.index];
|
||||
if (!this.residueIt.hasNext) {
|
||||
if (this.unit.model.atomicRanges.cyclicPolymerMap.has(this.residueSegment.index)) {
|
||||
this.state = AtomicPolymerBackboneIteratorState.cycle;
|
||||
} else {
|
||||
// TODO need to advance to a polymer that has two or more residues (can't assume it has)
|
||||
this.state = AtomicPolymerBackboneIteratorState.nextPolymer;
|
||||
}
|
||||
}
|
||||
} else if (this.state === AtomicPolymerBackboneIteratorState.cycle) {
|
||||
const { cyclicPolymerMap } = this.unit.model.atomicRanges;
|
||||
this.value.centerA.element = this.value.centerB.element;
|
||||
this.value.centerB.element = this.traceElementIndex[cyclicPolymerMap.get(this.residueSegment.index)!];
|
||||
// TODO need to advance to a polymer that has two or more residues (can't assume it has)
|
||||
this.state = AtomicPolymerBackboneIteratorState.nextPolymer;
|
||||
}
|
||||
|
||||
this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext || this.state === AtomicPolymerBackboneIteratorState.cycle;
|
||||
return this.value;
|
||||
}
|
||||
|
||||
constructor(structure: Structure, private unit: Unit.Atomic) {
|
||||
this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex>; // can assume it won't be -1 for polymer residues
|
||||
this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
|
||||
this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
this.value = createPolymerBackbonePair(structure, unit);
|
||||
this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext;
|
||||
}
|
||||
}
|
||||
|
||||
const enum CoarsePolymerBackboneIteratorState { nextPolymer, nextElement }
|
||||
|
||||
export class CoarsePolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
|
||||
private value: PolymerBackbonePair
|
||||
private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
|
||||
private polymerSegment: Segmentation.Segment<ResidueIndex>
|
||||
private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer
|
||||
private elementIndex: number
|
||||
hasNext: boolean = false;
|
||||
|
||||
move() {
|
||||
if (this.state === CoarsePolymerBackboneIteratorState.nextPolymer) {
|
||||
if (this.polymerIt.hasNext) {
|
||||
this.polymerSegment = this.polymerIt.move();
|
||||
this.elementIndex = this.polymerSegment.start;
|
||||
if (this.elementIndex + 1 < this.polymerSegment.end) {
|
||||
this.value.centerB.element = this.unit.elements[this.elementIndex];
|
||||
this.state = CoarsePolymerBackboneIteratorState.nextElement;
|
||||
} else {
|
||||
this.state = CoarsePolymerBackboneIteratorState.nextPolymer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state === CoarsePolymerBackboneIteratorState.nextElement) {
|
||||
this.elementIndex += 1;
|
||||
this.value.centerA.element = this.value.centerB.element;
|
||||
this.value.centerB.element = this.unit.elements[this.elementIndex];
|
||||
if (this.elementIndex + 1 >= this.polymerSegment.end) {
|
||||
this.state = CoarsePolymerBackboneIteratorState.nextPolymer;
|
||||
}
|
||||
}
|
||||
|
||||
this.hasNext = this.elementIndex + 1 < this.polymerSegment.end || this.polymerIt.hasNext;
|
||||
return this.value;
|
||||
}
|
||||
|
||||
constructor(structure: Structure, private unit: Unit.Spheres | Unit.Gaussians) {
|
||||
this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
|
||||
this.value = createPolymerBackbonePair(structure, unit);
|
||||
this.hasNext = this.polymerIt.hasNext;
|
||||
}
|
||||
}
|
||||
126
src/mol-repr/structure/visual/util/polymer/backbone.ts
Normal file
126
src/mol-repr/structure/visual/util/polymer/backbone.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
import { Segmentation } from '../../../../../mol-data/int';
|
||||
import { SortedRanges } from '../../../../../mol-data/int/sorted-ranges';
|
||||
import { ElementIndex, ResidueIndex, Unit } from '../../../../../mol-model/structure';
|
||||
import { MoleculeType } from '../../../../../mol-model/structure/model/types';
|
||||
import { getPolymerRanges } from '../polymer';
|
||||
|
||||
export type PolymerBackboneLinkCallback = (indexA: ElementIndex, indexB: ElementIndex, groupA: number, groupB: number, moleculeType: MoleculeType) => void
|
||||
|
||||
export function eachPolymerBackboneLink(unit: Unit, callback: PolymerBackboneLinkCallback) {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic: return eachAtomicPolymerBackboneLink(unit, callback);
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return eachCoarsePolymerBackboneLink(unit, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function eachAtomicPolymerBackboneLink(unit: Unit.Atomic, callback: PolymerBackboneLinkCallback) {
|
||||
const cyclicPolymerMap = unit.model.atomicRanges.cyclicPolymerMap;
|
||||
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
|
||||
const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
const traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex>; // can assume it won't be -1 for polymer residues
|
||||
const { moleculeType } = unit.model.atomicHierarchy.derived.residue;
|
||||
|
||||
let indexA = -1 as ResidueIndex;
|
||||
let indexB = -1 as ResidueIndex;
|
||||
let isFirst = true;
|
||||
let firstGroup = -1;
|
||||
let i = 0;
|
||||
while (polymerIt.hasNext) {
|
||||
isFirst = true;
|
||||
firstGroup = i;
|
||||
residueIt.setSegment(polymerIt.move());
|
||||
while (residueIt.hasNext) {
|
||||
if (isFirst) {
|
||||
const index_1 = residueIt.move().index;
|
||||
++i;
|
||||
if (!residueIt.hasNext)
|
||||
continue;
|
||||
isFirst = false;
|
||||
indexB = index_1;
|
||||
}
|
||||
const index = residueIt.move().index;
|
||||
indexA = indexB;
|
||||
indexB = index;
|
||||
callback(traceElementIndex[indexA], traceElementIndex[indexB], i - 1, i, moleculeType[indexA]);
|
||||
++i;
|
||||
}
|
||||
if (cyclicPolymerMap.has(indexB)) {
|
||||
indexA = indexB;
|
||||
indexB = cyclicPolymerMap.get(indexA)!;
|
||||
callback(traceElementIndex[indexA], traceElementIndex[indexB], i - 1, firstGroup, moleculeType[indexA]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function eachCoarsePolymerBackboneLink(unit: Unit.Spheres | Unit.Gaussians, callback: PolymerBackboneLinkCallback) {
|
||||
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
|
||||
const { elements } = unit;
|
||||
|
||||
let isFirst = true;
|
||||
let i = 0;
|
||||
while (polymerIt.hasNext) {
|
||||
isFirst = true;
|
||||
const _a = polymerIt.move(), start = _a.start, end = _a.end;
|
||||
for (let j = start, jl = end; j < jl; ++j) {
|
||||
if (isFirst) {
|
||||
++j;
|
||||
++i;
|
||||
if (j > jl)
|
||||
continue;
|
||||
isFirst = false;
|
||||
}
|
||||
callback(elements[j - 1], elements[j], i - 1, i, 0 /* Unknown */);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type PolymerBackboneElementCallback = (index: ElementIndex, group: number) => void
|
||||
|
||||
export function eachPolymerBackboneElement(unit: Unit, callback: PolymerBackboneElementCallback) {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic: return eachAtomicPolymerBackboneElement(unit, callback);
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return eachCoarsePolymerBackboneElement(unit, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export function eachAtomicPolymerBackboneElement(unit: Unit.Atomic, callback: PolymerBackboneElementCallback) {
|
||||
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
|
||||
const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
const traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex>; // can assume it won't be -1 for polymer residues
|
||||
|
||||
let i = 0;
|
||||
while (polymerIt.hasNext) {
|
||||
residueIt.setSegment(polymerIt.move());
|
||||
while (residueIt.hasNext) {
|
||||
const index = residueIt.move().index;
|
||||
callback(traceElementIndex[index], i);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function eachCoarsePolymerBackboneElement(unit: Unit.Spheres | Unit.Gaussians, callback: PolymerBackboneElementCallback) {
|
||||
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
|
||||
const { elements } = unit;
|
||||
|
||||
let i = 0;
|
||||
while (polymerIt.hasNext) {
|
||||
const _a = polymerIt.move(), start = _a.start, end = _a.end;
|
||||
for (let j = start, jl = end; j < jl; ++j) {
|
||||
callback(elements[j], i);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../../../../mol-math/linear-algebra';
|
||||
import { NumberArray } from '../../../../../mol-util/type-helpers';
|
||||
import { lerp } from '../../../../../mol-math/interpolate';
|
||||
import { lerp, smoothstep } from '../../../../../mol-math/interpolate';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
@@ -19,6 +19,8 @@ const v3copy = Vec3.copy;
|
||||
const v3cross = Vec3.cross;
|
||||
const v3orthogonalize = Vec3.orthogonalize;
|
||||
const v3matchDirection = Vec3.matchDirection;
|
||||
const v3scale = Vec3.scale;
|
||||
const v3add = Vec3.add;
|
||||
|
||||
export interface CurveSegmentState {
|
||||
curvePoints: NumberArray,
|
||||
@@ -92,6 +94,7 @@ const tangentVec = Vec3();
|
||||
const normalVec = Vec3();
|
||||
const binormalVec = Vec3();
|
||||
const prevNormal = Vec3();
|
||||
const nextNormal = Vec3();
|
||||
const firstTangentVec = Vec3();
|
||||
const lastTangentVec = Vec3();
|
||||
const firstNormalVec = Vec3();
|
||||
@@ -116,8 +119,10 @@ export function interpolateNormals(state: CurveSegmentState, controls: CurveSegm
|
||||
|
||||
v3copy(prevNormal, firstNormalVec);
|
||||
|
||||
const n1 = n - 1;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const t = i === 0 ? 0 : 1 / (n - i);
|
||||
const j = smoothstep(0, n1, i) * n1;
|
||||
const t = i === 0 ? 0 : 1 / (n - j);
|
||||
|
||||
v3fromArray(tangentVec, tangentVectors, i * 3);
|
||||
|
||||
@@ -129,6 +134,19 @@ export function interpolateNormals(state: CurveSegmentState, controls: CurveSegm
|
||||
v3normalize(binormalVec, v3cross(binormalVec, tangentVec, normalVec));
|
||||
v3toArray(binormalVec, binormalVectors, i * 3);
|
||||
}
|
||||
|
||||
for (let i = 1; i < n1; ++i) {
|
||||
v3fromArray(prevNormal, normalVectors, (i - 1) * 3);
|
||||
v3fromArray(normalVec, normalVectors, i * 3);
|
||||
v3fromArray(nextNormal, normalVectors, (i + 1) * 3);
|
||||
|
||||
v3scale(normalVec, v3add(normalVec, prevNormal, v3add(normalVec, nextNormal, normalVec)), 1 / 3);
|
||||
v3toArray(normalVec, normalVectors, i * 3);
|
||||
|
||||
v3fromArray(tangentVec, tangentVectors, i * 3);
|
||||
v3normalize(binormalVec, v3cross(binormalVec, tangentVec, normalVec));
|
||||
v3toArray(binormalVec, binormalVectors, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
export function interpolateSizes(state: CurveSegmentState, w0: number, w1: number, w2: number, h0: number, h1: number, h2: number, shift: number) {
|
||||
|
||||
@@ -123,6 +123,6 @@ export const SecondaryStructureColorThemeProvider: ColorTheme.Provider<Secondary
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? SecondaryStructureProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(SecondaryStructureProvider.descriptor, false)
|
||||
detach: (data) => data.structure && SecondaryStructureProvider.ref(data.structure, false)
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -105,6 +105,29 @@ export function arraySetRemove<T>(xs: T[], x: T) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caution, O(n^2) complexity. Only use for small input sizes.
|
||||
* For larger inputs consider using `SortedArray`.
|
||||
*/
|
||||
export function arrayAreIntersecting<T>(xs: T[], ys: T[]) {
|
||||
for (let i = 0, il = xs.length; i < il; ++i) {
|
||||
if (ys.includes(xs[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caution, O(n^2) complexity. Only use for small input sizes.
|
||||
* For larger inputs consider using `SortedArray`.
|
||||
*/
|
||||
export function arrayIntersectionSize<T>(xs: T[], ys: T[]) {
|
||||
let count = 0;
|
||||
for (let i = 0, il = xs.length; i < il; ++i) {
|
||||
if (ys.includes(xs[i])) count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
export function arrayEqual<T>(xs?: ArrayLike<T>, ys?: ArrayLike<T>) {
|
||||
if (!xs || xs.length === 0) return !ys || ys.length === 0;
|
||||
if (!ys) return false;
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -32,4 +32,16 @@ namespace PixelData {
|
||||
}
|
||||
return pixelData;
|
||||
}
|
||||
|
||||
/** to undo pre-multiplied alpha */
|
||||
export function divideByAlpha(pixelData: PixelData): PixelData {
|
||||
const { array } = pixelData;
|
||||
for (let i = 0, il = array.length; i < il; i += 4) {
|
||||
const a = array[i + 3] / 255;
|
||||
array[i] /= a;
|
||||
array[i + 1] /= a;
|
||||
array[i + 2] /= a;
|
||||
}
|
||||
return pixelData;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user