Compare commits

...

108 Commits

Author SHA1 Message Date
dsehnal
8d3ac92989 2.2.3 2021-08-25 17:40:28 +02:00
dsehnal
d6eb334d12 changelog 2021-08-25 17:38:28 +02:00
Alexander Rose
77139afe7f tweak Interval.size 2021-08-21 14:00:51 -07:00
Alexander Rose
54476ad85e improve print texture debug helpers 2021-08-21 13:59:38 -07:00
Alexander Rose
6dd876232d avoid superfluous calls to Loci.isWholeStructure 2021-08-21 13:51:40 -07:00
Alexander Rose
ae2314d76c fix camera/bounding helper not showing up 2021-08-21 13:48:56 -07:00
David Sehnal
6667509745 Merge pull request #257 from JonStargaryen/master
ANVIL: improve prediction for 3pqr
2021-08-21 20:22:38 +02:00
JonStargaryen
5c871a5aae ANVIL: increase number of sphere points to 175 2021-08-20 12:43:24 -07:00
Alexander Rose
2482ef92af Improved StructureElement.Loci.size performance
- inlined code
- important for marking large cellpack models
2021-08-15 14:42:49 -07:00
Alexander Rose
db59303a84 Merge pull request #244 from molstar/meshproc
Mesh processing: border smoothing
2021-08-14 19:36:23 -07:00
Alexander Rose
fe700953ff Merge branch 'master' into meshproc 2021-08-14 19:32:06 -07:00
Alexander Rose
047946e41c remove superfluous type casts 2021-08-14 19:30:16 -07:00
dsehnal
f833efae37 2.2.2 2021-08-11 14:54:03 +02:00
dsehnal
be4b787e66 Fix mol-script query compiler const expression recognition 2021-08-11 14:49:52 +02:00
David Sehnal
950b1c179a Merge pull request #248 from MadCatX/fix-isosurface
Do not cache LevelTexturesFramebuffers as they may become invalid
2021-08-10 12:40:52 +02:00
Michal Malý
2bd1a01afb Do not attach framebuffer unnecessarily 2021-08-10 09:15:05 +02:00
dsehnal
2fe43eda2b Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-08-09 20:33:57 +02:00
dsehnal
45fc0c61af Mesh.smoothEdges options 2021-08-09 20:32:40 +02:00
dsehnal
7e7993f5ba improve fillEdges in mesh edge smoothing
- sort boundary vertices
- limit the length added edges
2021-08-09 20:22:13 +02:00
Michal Malý
9d34dbff0f Do not cache LevelTexturesFramebuffers as they may become invalid 2021-08-09 09:58:55 +02:00
Alexander Rose
ba1b03f01b fix TransformData issues, see #133
- handle structure vs structure.root in ExplodeStructureRepresentation3D and SpinStructureRepresentation3D
2021-08-08 13:11:50 -07:00
Alexander Rose
791f7ca3c8 Merge pull request #245 from sukolsak/optimize-setCylinderMat 2021-08-08 10:45:23 -07:00
Sukolsak Sakshuwong
c14d50e4ff optimize setCylinderMat() 2021-08-08 08:51:16 -07:00
Alexander Rose
3e9de449c8 cleanup 2021-08-07 19:45:47 -07:00
Alexander Rose
0132c7ef5e Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-08-07 19:42:41 -07:00
Alexander Rose
aa2222c086 remove clipSphere option & param cleanup
- clip objects are better
- includeParent option not useful for gaussian-surface visuals
2021-08-07 19:20:51 -07:00
dsehnal
fc2765d376 2.2.1 2021-08-02 18:11:24 +02:00
dsehnal
9d85194082 2.2.1 changelog 2021-08-02 18:09:50 +02:00
David Sehnal
abfcc60898 Merge pull request #243 from molstar/input-observer-improvements
Input observer improvements
2021-08-02 18:08:48 +02:00
dsehnal
c688a83fa2 fix typo 2021-08-02 18:07:34 +02:00
dsehnal
77376056b9 changelog 2021-08-02 18:03:07 +02:00
dsehnal
8efd943c2b PinchInput.fractionDelta 2021-08-02 17:24:49 +02:00
dsehnal
b230655439 fix type 2021-08-02 16:55:06 +02:00
dsehnal
8ba792c4b0 add maxWheelDelta 2021-08-02 16:50:09 +02:00
dsehnal
fde8ca69e4 support for Safari gestures (pinch zoom on MacBook trackpad) 2021-08-02 16:30:48 +02:00
dsehnal
9ee1439299 normalize wheel speed in input observer 2021-08-02 14:45:25 +02:00
David Sehnal
195668760e Merge pull request #242 from sukolsak/export-overpaint
Add overpaint support to geometry exporters
2021-08-02 13:04:09 +02:00
Sukolsak Sakshuwong
bd64f1db9a update changelog and fix type 2021-08-02 01:19:31 -07:00
Sukolsak Sakshuwong
38a5a857aa refactor color calculation 2021-08-01 23:09:30 -07:00
Sukolsak Sakshuwong
5e8cdfe3a7 add overpaint support to geometry exporters 2021-08-01 22:30:05 -07:00
Alexander Rose
f892917e1c Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-08-01 14:04:50 -07:00
Alexander Rose
738b7f4ca5 Merge pull request #238 from molstar/dynbonds
Bond improvements (mostly IndexPairBonds)
2021-08-01 13:53:42 -07:00
Alexander Rose
bce53d03a5 bond tweaks
- add DefaultBondMaxRadius constant
- add IndexPairBonds.Props object
2021-08-01 13:49:44 -07:00
Alexander Rose
74f721ab9f improve Structure.asParent
- handle parent coordinate system not identity
2021-08-01 13:48:26 -07:00
Alexander Rose
f011025f16 changelog 2021-07-31 23:22:11 -07:00
Alexander Rose
9e44cd83fa add clipSphere param to molecular-surface mesh 2021-07-31 23:21:57 -07:00
Alexander Rose
e0e45b64ac optimize invertCantorPairing 2021-07-31 23:15:33 -07:00
Alexander Rose
f10b152252 Merge branch 'master' of https://github.com/molstar/molstar into meshproc 2021-07-31 14:42:18 -07:00
Alexander Rose
23cf5c2fdd changelog and docs 2021-07-31 14:22:45 -07:00
Alexander Rose
e211abd5ae Merge branch 'master' of https://github.com/molstar/molstar into dynbonds 2021-07-31 14:11:45 -07:00
Alexander Rose
7199be4d62 tweak getColorSmoothingProps
- make independent of webgl context
2021-07-31 14:10:50 -07:00
Alexander Rose
e1a40ded1d add surronding atoms structure selection query 2021-07-31 13:53:49 -07:00
Alexander Rose
8999c3097d handle dynamicBonds in root structure helper 2021-07-31 13:50:07 -07:00
Alexander Rose
44308fa1fd add maxDistance prop to IndexPairBonds 2021-07-31 13:48:10 -07:00
Alexander Rose
f5dd2f4579 support coordinateSystem in structure.asParent 2021-07-31 13:45:39 -07:00
dsehnal
104999b7dc 2.2.0 2021-07-31 15:15:09 +02:00
dsehnal
e5341623d3 changelog v2.2.0 2021-07-31 15:12:40 +02:00
dsehnal
0e9238e5ec Canvas3D tweaks:
- update "forceDraw" logic
- Ensure the scene is re-rendered when viewport size changes
- Support noDraw mode in PluginAnimationLoop
2021-07-31 15:06:58 +02:00
dsehnal
43c292e2df Support new EMDB API for EM volume contour levels 2021-07-31 14:12:33 +02:00
dsehnal
fbfd1b20d8 Prefer _label_seq_id fields in secondary structure assignment 2021-07-31 13:54:11 +02:00
dsehnal
5330df87e1 Merge branch 'master' of https://github.com/molstar/molstar 2021-07-27 12:29:17 +02:00
dsehnal
ad6b3c6fe0 add DS_store to .gitignore 2021-07-27 12:28:56 +02:00
Alexander Rose
b983df7eb5 mesh edge smoothing 2021-07-25 20:26:17 -07:00
Alexander Rose
add76a87d9 remove unnecessary check
- see 7686b61728
2021-07-25 20:22:00 -07:00
Alexander Rose
f9f8350d28 dynamic pair bonds on coordinate changes
- add dynamicBonds structure parameter
- add maxRadius, ignoreWater bond compute parameters
- ensure inter unit bond visuals are recreated
2021-07-24 17:16:59 -07:00
Alexander Rose
b71c2f365c add operator Loci granularity 2021-07-24 16:55:07 -07:00
Alexander Rose
a5443189d3 missing param 2021-07-24 16:46:30 -07:00
Alexander Rose
7686b61728 fix includeParent for multi instance bond visuals 2021-07-24 16:31:26 -07:00
Alexander Rose
844c13cd35 2.2.0-dev.1 2021-07-20 21:08:51 -07:00
Alexander Rose
d1c8b92fdf Merge pull request #224 from sukolsak/usdz-export
USDZ export
2021-07-18 09:28:27 -07:00
Sukolsak Sakshuwong
93d33bca80 view USDZ in AR on iOS 2021-07-17 21:41:26 -07:00
Sukolsak Sakshuwong
6550e53414 Merge branch 'master' into usdz-export 2021-07-17 18:50:43 -07:00
Alexander Rose
96dddb0998 updated changelog 2021-07-17 10:54:34 -07:00
Alexander Rose
baa64d8109 handle mononucleotides when guessing component type 2021-07-17 10:42:56 -07:00
David Sehnal
2df145aa8f Merge pull request #231 from molstar/sdf-parser-improvements
Sdf parser improvements
2021-07-16 18:27:16 +02:00
dsehnal
06b9c5f2de change SDF data header parsing
- do not trim <> around field
- store whole line staring with '> ' as data header (without the staring '> ')
2021-07-16 18:25:22 +02:00
dsehnal
e03b689f27 add SdfFormat 2021-07-16 18:04:25 +02:00
Sukolsak Sakshuwong
e4cdcff3ee Merge branch 'master' into usdz-export 2021-07-12 00:11:24 -07:00
Alexander Rose
f73150d074 Merge pull request #225 from molstar/tubular-helices
Add tubularHelices parameter to Cartoon representation
2021-07-11 11:45:38 -07:00
Alexander Rose
451dc12689 cleanup 2021-07-11 11:31:02 -07:00
Alexander Rose
a3fb7762d8 add tubular helices to Cartoon representation 2021-07-10 15:41:54 -07:00
Alexander Rose
3dfafc3202 handle cell angles close to zero in dcd reader 2021-07-10 15:35:53 -07:00
Alexander Rose
4fcea991d3 set default outline scale back to 1 2021-07-10 15:28:49 -07:00
Alexander Rose
0607ed46d1 handle more common ff residue/atom names 2021-07-10 15:28:23 -07:00
Sukolsak Sakshuwong
30d6244e82 add support for USDZ 2021-07-10 05:19:42 -07:00
Sukolsak Sakshuwong
fab8c74365 move quantizeColors to MeshExporter 2021-07-09 13:10:09 -07:00
Sukolsak Sakshuwong
1952922e4e make RenderObjectExporter.getData async 2021-07-09 13:10:09 -07:00
Alexander Rose
1eb351369e 2.1.0 2021-07-05 16:11:46 -07:00
Alexander Rose
701d782485 changelog 2021-07-05 16:07:30 -07:00
Alexander Rose
dc8457c4dc smoother trace normals 2021-07-05 15:59:28 -07:00
Alexander Rose
f104cd4d11 add missing import 2021-07-05 13:47:39 -07:00
Alexander Rose
9b56a6ae65 Merge pull request #221 from molstar/aromatic
Aromatic bond display option
2021-07-05 13:42:11 -07:00
Alexander Rose
2485ad5a2f Merge branch 'master' into aromatic 2021-07-05 13:38:05 -07:00
Alexander Rose
a56716ab6a Merge pull request #222 from molstar/backbone
Backbone representation
2021-07-05 13:37:24 -07:00
Alexander Rose
e0aaaa989e Merge branch 'master' into backbone 2021-07-05 13:34:40 -07:00
Alexander Rose
9ec0f9e736 outline fixes and improvements
- better handle outlines in orthographic mode
- remove unused code
- increase default outline scale to 2
2021-07-05 13:31:00 -07:00
Alexander Rose
47968eeeec fix traceOnly handling in makeElementIgnoreTest 2021-07-04 14:54:52 -07:00
Alexander Rose
9c157b70e1 warning for arrayAreIntersecting/arrayIntersectionSize 2021-07-04 14:50:58 -07:00
Alexander Rose
6d7e4ca227 remove unused import 2021-07-04 14:48:06 -07:00
Alexander Rose
fccd08d2ec add backbone repr
- atomistic and coarse units
2021-07-04 14:39:56 -07:00
Alexander Rose
19bae202d0 handle Vec3.angle edge case 2021-07-04 14:35:46 -07:00
Alexander Rose
4ba0ae24e4 support aromatic bond display as dashes
- add arrayAreIntersecting/arrayIntersectionSize helpers
- prefer reference atoms within rings (also for double/triple bonds)
2021-07-03 23:12:06 -07:00
Alexander Rose
fcf3718d75 better parsing of mol2 bond types 2021-07-03 22:38:29 -07:00
Alexander Rose
df1dd94f1c fix BondType.Names 2021-07-03 22:35:35 -07:00
Alexander Rose
65ba401850 fix repr update for Structure.asParent objects 2021-07-03 22:26:20 -07:00
Alexander Rose
a98f5e1047 fix fxaa antialiasing
- was broken when used with other postprocessing effects
- expose texture.filter
2021-07-03 22:19:51 -07:00
David Sehnal
e5cf97d1ea Merge pull request #217 from sukolsak/fix-cylinder
Fix cylinder orientation
2021-06-28 14:45:38 +02:00
Sukolsak Sakshuwong
1844fc14b2 fix cylinder orientation 2021-06-27 13:14:30 -07:00
109 changed files with 2674 additions and 915 deletions

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ tsconfig.commonjs.tsbuildinfo
*.sublime-workspace
.idea
.DS_Store

View File

@@ -7,6 +7,58 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v2.2.3] - 2021-08-25
- Add ``invertCantorPairing`` helper function
- Add ``Mesh`` processing helper ``.smoothEdges``
- Smooth border of molecular-surface with ``includeParent`` enabled
- Hide ``includeParent`` option from gaussian-surface visuals (not particularly useful)
- Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models)
- Fix new ``TransformData`` issues (camera/bounding helper not showing up)
- Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``)
## [v2.2.2] - 2021-08-11
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
- Fix ``mol-script`` query compiler const expression recognition.
## [v2.2.1] - 2021-08-02
- Add surrounding atoms (5 Angstrom) structure selection query
- [Breaking] Add maxDistance prop to ``IndexPairBonds``
- Fix coordinateSystem not handled in ``Structure.asParent``
- Add ``dynamicBonds`` to ``Structure`` props (force re-calc on model change)
- Expose as optional param in root structure transform helper
- Add overpaint support to geometry exporters
- ``InputObserver`` improvements
- normalize wheel speed across browsers/platforms
- support Safari gestures (used by ``TrackballControls``)
- ``PinchInput.fractionDelta`` and use it in ``TrackballControls``
## [v2.2.0] - 2021-07-31
- Add ``tubularHelices`` parameter to Cartoon representation
- Add ``SdfFormat`` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
- Fix mononucleotides detected as polymer components (#229)
- Set default outline scale back to 1
- Improved DCD reader cell angle handling (interpret near 0 angles as 90 deg)
- Handle more residue/atom names commonly used in force-fields
- Add USDZ support to ``geo-export`` extension.
- Fix ``includeParent`` support for multi-instance bond visuals.
- Add ``operator`` Loci granularity, selecting everything with the same operator name.
- Prefer ``_label_seq_id`` fields in secondary structure assignment.
- Support new EMDB API (https://www.ebi.ac.uk/emdb/api/entry/map/[EMBD-ID]) for EM volume contour levels.
- ``Canvas3D`` tweaks:
- Update ``forceDraw`` logic.
- Ensure the scene is re-rendered when viewport size changes.
- Support ``noDraw`` mode in ``PluginAnimationLoop``.
## [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.
@@ -33,8 +85,8 @@ Note that since we don't clearly distinguish between a public and private interf
- Add ability to select residues from a list of identifiers to the Selection UI.
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
- Add `MeshBuilder.addMesh`.
- Add `Torus` primitive.
- Add ``MeshBuilder.addMesh``.
- Add ``Torus`` primitive.
- Lazy volume loading support.
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
@@ -52,12 +104,12 @@ Note that since we don't clearly distinguish between a public and private interf
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
### Changed
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- `Canvas3D.getRenderObjects`.
- ``Canvas3D.getRenderObjects``.
- [WIP] Animate state interpolating, including model trajectories
### Changed

4
package-lock.json generated
View File

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

View File

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

View File

@@ -39,7 +39,7 @@ interface ANVILContext {
};
export const ANVILParams = {
numberOfSpherePoints: PD.Numeric(140, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
numberOfSpherePoints: PD.Numeric(175, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),

View File

@@ -20,10 +20,10 @@ import { Structure, StructureProperties, Unit } from '../../../mol-model/structu
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
import { StructureGroup, UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { VisualContext } from '../../../mol-repr/visual';
import { getAltResidueLociFromId } from '../../../mol-repr/structure/visual/util/common';
import { getAltResidueLociFromId, StructureGroup } from '../../../mol-repr/structure/visual/util/common';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
import { NullLocation } from '../../../mol-model/location';

View File

@@ -13,15 +13,17 @@ import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateSelection } from '../../mol-state';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { SetUtils } from '../../mol-util/set';
import { ObjExporter } from './obj-exporter';
import { GlbExporter } from './glb-exporter';
import { ObjExporter } from './obj-exporter';
import { StlExporter } from './stl-exporter';
import { UsdzExporter } from './usdz-exporter';
export const GeometryParams = {
format: PD.Select('glb', [
['glb', 'glTF 2.0 Binary (.glb)'],
['stl', 'Stl (.stl)'],
['obj', 'Wavefront (.obj)']
['obj', 'Wavefront (.obj)'],
['usdz', 'Universal Scene Description (.usdz)']
])
};
@@ -44,11 +46,12 @@ export class GeometryControls extends PluginComponent {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const boundingBox = Box3D.fromSphere3D(Box3D(), this.plugin.canvas3d?.boundingSphereVisible!);
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter;
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
switch (this.behaviors.params.value.format) {
case 'glb':
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
renderObjectExporter = new GlbExporter(style, boundingBox);
break;
case 'obj':
@@ -57,6 +60,9 @@ export class GeometryControls extends PluginComponent {
case 'stl':
renderObjectExporter = new StlExporter(boundingBox);
break;
case 'usdz':
renderObjectExporter = new UsdzExporter(style, boundingBox, boundingSphere.radius);
break;
default: throw new Error('Unsupported format.');
}

View File

@@ -126,11 +126,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
};
}
private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array) {
private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined) {
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const uColor = values.uColor.ref.value;
const tColor = values.tColor.ref.value.array;
const uAlpha = values.uAlpha.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value;
@@ -138,38 +135,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
const colorArray = new Uint8Array(vertexCount * 4);
for (let i = 0; i < vertexCount; ++i) {
let color: Color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(uColor, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
}
case 'vertex':
color = Color.fromArray(tColor, i * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * vertexCount + i) * 3);
break;
case 'volume':
color = Color.fromArray(interpolatedColors!, i * 3);
break;
case 'volumeInstance':
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + i) * 3);
break;
default: throw new Error('Unsupported color type.');
}
let color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i);
let alpha = uAlpha;
if (dTransparency) {
@@ -201,7 +167,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
@@ -235,7 +201,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
// create a color buffer if needed
if (instanceIndex === 0 || !sameColorBuffer) {
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors!);
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors);
}
// glTF mesh
@@ -264,7 +230,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
}
}
getData() {
async getData() {
const binaryBufferLength = this.byteOffset;
const gltf = {
@@ -334,7 +300,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
}
async getBlob(ctx: RuntimeContext) {
return new Blob([this.getData().glb], { type: 'model/gltf-binary' });
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
}
constructor(private style: Style, boundingBox: Box3D) {

View File

@@ -4,6 +4,7 @@
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { sort, arraySwap } from '../../mol-data/util';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { MeshValues } from '../../mol-gl/renderable/mesh';
import { LinesValues } from '../../mol-gl/renderable/lines';
@@ -22,6 +23,7 @@ import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
import { Vec3 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
@@ -111,6 +113,70 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
return interpolated.array;
}
protected static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
if (vertexCount <= 1024) return;
const rgb = Vec3();
const min = Vec3();
const max = Vec3();
const sum = Vec3();
const colorMap = new Map<Color, Color>();
const colorComparers = [
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
];
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
if (l > r) return;
if (l === r || depth >= 10) {
// Find the average color.
Vec3.set(sum, 0, 0, 0);
for (let i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
Vec3.add(sum, sum, rgb);
}
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
const averageColor = Color.fromArray(rgb, 0);
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
return;
}
// Find the color channel with the greatest range.
Vec3.set(min, 255, 255, 255);
Vec3.set(max, 0, 0, 0);
for (let i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
for (let j = 0; j < 3; ++j) {
Vec3.min(min, min, rgb);
Vec3.max(max, max, rgb);
}
}
let k = 0;
if (max[1] - min[1] > max[k] - min[k]) k = 1;
if (max[2] - min[2] > max[k] - min[k]) k = 2;
sort(colors, l, r + 1, colorComparers[k], arraySwap);
const m = (l + r) >> 1;
medianCut(colors, l, m, depth + 1);
medianCut(colors, m + 1, r, depth + 1);
};
// Create an array of unique colors and use the median cut algorithm.
const colorSet = new Set<Color>();
for (let i = 0; i < vertexCount; ++i) {
colorSet.add(Color.fromArray(colorArray, i * 3));
}
const colors = Array.from(colorSet);
medianCut(colors, 0, colors.length - 1, 0);
// Map actual colors to quantized colors.
for (let i = 0; i < vertexCount; ++i) {
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
Color.toArray(color!, colorArray, i * 3);
}
}
protected static getInstance(input: AddMeshInput, instanceIndex: number) {
const { mesh, meshes } = input;
if (mesh !== undefined) {
@@ -128,6 +194,57 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
}
}
protected static getColor(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined, vertexIndex: number): Color {
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const uColor = values.uColor.ref.value;
const tColor = values.tColor.ref.value.array;
const dOverpaint = values.dOverpaint.ref.value;
const tOverpaint = values.tOverpaint.ref.value.array;
let color: Color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(uColor, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
}
case 'vertex':
color = Color.fromArray(tColor, vertexIndex * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * vertexCount + vertexIndex) * 3);
break;
case 'volume':
color = Color.fromArray(interpolatedColors!, vertexIndex * 3);
break;
case 'volumeInstance':
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + vertexIndex) * 3);
break;
default: throw new Error('Unsupported color type.');
}
if (dOverpaint) {
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
const overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4);
const overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255;
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
}
return color;
}
protected abstract addMeshWithColors(input: AddMeshInput): void;
private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
@@ -278,7 +395,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
}
}
abstract getData(): D;
abstract getData(ctx: RuntimeContext): Promise<D>;
abstract getBlob(ctx: RuntimeContext): Promise<Blob>;
}

View File

@@ -4,7 +4,6 @@
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
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';
@@ -69,70 +68,6 @@ export class ObjExporter extends MeshExporter<ObjData> {
}
}
private static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
if (vertexCount <= 1024) return;
const rgb = Vec3();
const min = Vec3();
const max = Vec3();
const sum = Vec3();
const colorMap = new Map<Color, Color>();
const colorComparers = [
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
];
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
if (l > r) return;
if (l === r || depth >= 10) {
// Find the average color.
Vec3.set(sum, 0, 0, 0);
for (let i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
Vec3.add(sum, sum, rgb);
}
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
const averageColor = Color.fromArray(rgb, 0);
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
return;
}
// Find the color channel with the greatest range.
Vec3.set(min, 255, 255, 255);
Vec3.set(max, 0, 0, 0);
for (let i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
for (let j = 0; j < 3; ++j) {
Vec3.min(min, min, rgb);
Vec3.max(max, max, rgb);
}
}
let k = 0;
if (max[1] - min[1] > max[k] - min[k]) k = 1;
if (max[2] - min[2] > max[k] - min[k]) k = 2;
sort(colors, l, r + 1, colorComparers[k], arraySwap);
const m = (l + r) >> 1;
medianCut(colors, l, m, depth + 1);
medianCut(colors, m + 1, r, depth + 1);
};
// Create an array of unique colors and use the median cut algorithm.
const colorSet = new Set<Color>();
for (let i = 0; i < vertexCount; ++i) {
colorSet.add(Color.fromArray(colorArray, i * 3));
}
const colors = Array.from(colorSet);
medianCut(colors, 0, colors.length - 1, 0);
// Map actual colors to quantized colors.
for (let i = 0; i < vertexCount; ++i) {
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
Color.toArray(color!, colorArray, i * 3);
}
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
@@ -144,14 +79,13 @@ export class ObjExporter extends MeshExporter<ObjData> {
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const tColor = values.tColor.ref.value.array;
const uAlpha = values.uAlpha.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
@@ -194,38 +128,8 @@ export class ObjExporter extends MeshExporter<ObjData> {
// face
for (let i = 0; i < drawCount; i += 3) {
let color: Color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
}
case 'vertex':
color = Color.fromArray(tColor, indices![i] * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * vertexCount + indices![i]) * 3);
break;
case 'volume':
color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);
break;
case 'volumeInstance':
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + (isGeoTexture ? i : indices![i])) * 3);
break;
default: throw new Error('Unsupported color type.');
}
const v = isGeoTexture ? i : indices![i];
const color = ObjExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
let alpha = uAlpha;
if (dTransparency) {
@@ -256,7 +160,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
}
}
getData() {
async getData() {
return {
obj: StringBuilder.getString(this.obj),
mtl: StringBuilder.getString(this.mtl)
@@ -264,7 +168,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
}
async getBlob(ctx: RuntimeContext) {
const { obj, mtl } = this.getData();
const { obj, mtl } = await this.getData();
const objData = new Uint8Array(obj.length);
asciiWrite(objData, obj);
const mtlData = new Uint8Array(mtl.length);

View File

@@ -9,12 +9,12 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
import { RuntimeContext } from '../../mol-task';
export type RenderObjectExportData = {
[k: string]: string | Uint8Array | undefined
[k: string]: string | Uint8Array | ArrayBuffer | undefined
}
export interface RenderObjectExporter<D extends RenderObjectExportData> {
readonly fileExtension: string
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
getData(): D
getData(ctx: RuntimeContext): Promise<D>
getBlob(ctx: RuntimeContext): Promise<Blob>
}

View File

@@ -89,7 +89,7 @@ export class StlExporter extends MeshExporter<StlData> {
}
}
getData() {
async getData() {
const stl = new Uint8Array(84 + 50 * this.triangleCount);
asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`);
@@ -106,7 +106,7 @@ export class StlExporter extends MeshExporter<StlData> {
}
async getBlob(ctx: RuntimeContext) {
return new Blob([this.getData().stl], { type: 'model/stl' });
return new Blob([(await this.getData()).stl], { type: 'model/stl' });
}
constructor(boundingBox: Box3D) {

View File

@@ -7,7 +7,7 @@
import { merge } from 'rxjs';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button } from '../../mol-plugin-ui/controls/common';
import { GetAppSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
import { GetAppSvg, CubeScanSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { download } from '../../mol-util/download';
import { GeometryParams, GeometryControls } from './controls';
@@ -18,6 +18,7 @@ interface State {
export class GeometryExporterUI extends CollapsableControls<{}, State> {
private _controls: GeometryControls | undefined;
private isARSupported: boolean | undefined;
get controls() {
return this._controls || (this._controls = new GeometryControls(this.plugin));
@@ -32,6 +33,9 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
}
protected renderControls(): JSX.Element {
if (this.isARSupported === undefined) {
this.isARSupported = !!document.createElement('a').relList?.supports?.('ar');
}
const ctrl = this.controls;
return <>
<ParameterControls
@@ -45,6 +49,13 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
Save
</Button>
{this.isARSupported && ctrl.behaviors.params.value.format === 'usdz' &&
<Button icon={CubeScanSvg}
onClick={this.viewInAR} style={{ marginTop: 1 }}
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
View in AR
</Button>
}
</>;
}
@@ -75,4 +86,22 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
this.setState({ busy: false });
}
}
viewInAR = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.exportGeometry();
this.setState({ busy: false });
const a = document.createElement('a');
a.rel = 'ar';
a.href = URL.createObjectURL(data.blob);
// For in-place viewing of USDZ on iOS, the link must contain a single child that is either an img or picture.
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/
a.appendChild(document.createElement('img'));
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
} catch {
this.setState({ busy: false });
}
}
}

View File

@@ -0,0 +1,231 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
import { PLUGIN_VERSION } from '../../mol-plugin/version';
import { RuntimeContext } from '../../mol-task';
import { StringBuilder } from '../../mol-util';
import { Color } from '../../mol-util/color/color';
import { zip } from '../../mol-util/zip/zip';
import { MeshExporter, AddMeshInput } from './mesh-exporter';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4 = Vec3.transformMat4;
const v3transformMat3 = Vec3.transformMat3;
const mat3directionTransform = Mat3.directionTransform;
// https://graphics.pixar.com/usd/docs/index.html
export type UsdzData = {
usdz: ArrayBuffer
}
export class UsdzExporter extends MeshExporter<UsdzData> {
readonly fileExtension = 'usdz';
private meshes: string[] = [];
private materials: string[] = [];
private materialSet = new Set<number>();
private centerTransform: Mat4;
private static getMaterialKey(color: Color, alpha: number) {
return color * 256 + Math.round(alpha * 255);
}
private addMaterial(color: Color, alpha: number) {
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
if (this.materialSet.has(materialKey)) return;
this.materialSet.add(materialKey);
const [r, g, b] = Color.toRgbNormalized(color);
this.materials.push(`
def Material "material${materialKey}"
{
token outputs:surface.connect = </material${materialKey}/shader.outputs:surface>
def Shader "shader"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (${r},${g},${b})
float inputs:opacity = ${alpha}
float inputs:metallic = ${this.style.metalness}
float inputs:roughness = ${this.style.roughness}
token outputs:surface
}
}
`);
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
const t = Mat4();
const n = Mat3();
const tmpV = Vec3();
const stride = isGeoTexture ? 4 : 3;
const groupCount = values.uGroupCount.ref.value;
const colorType = values.dColorType.ref.value;
const uAlpha = values.uAlpha.ref.value;
const dTransparency = values.dTransparency.ref.value;
const tTransparency = values.tTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
}
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
const { vertices, normals, indices, groups, vertexCount, drawCount } = UsdzExporter.getInstance(input, instanceIndex);
Mat4.fromArray(t, aTransform, instanceIndex * 16);
Mat4.mul(t, this.centerTransform, t);
mat3directionTransform(n, t);
const vertexBuilder = StringBuilder.create();
const normalBuilder = StringBuilder.create();
const indexBuilder = StringBuilder.create();
// position
for (let i = 0; i < vertexCount; ++i) {
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
StringBuilder.writeSafe(vertexBuilder, (i === 0) ? '(' : ',(');
StringBuilder.writeFloat(vertexBuilder, tmpV[0], 10000);
StringBuilder.writeSafe(vertexBuilder, ',');
StringBuilder.writeFloat(vertexBuilder, tmpV[1], 10000);
StringBuilder.writeSafe(vertexBuilder, ',');
StringBuilder.writeFloat(vertexBuilder, tmpV[2], 10000);
StringBuilder.writeSafe(vertexBuilder, ')');
}
// normal
for (let i = 0; i < vertexCount; ++i) {
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
StringBuilder.writeSafe(normalBuilder, ',');
StringBuilder.writeFloat(normalBuilder, tmpV[1], 100);
StringBuilder.writeSafe(normalBuilder, ',');
StringBuilder.writeFloat(normalBuilder, tmpV[2], 100);
StringBuilder.writeSafe(normalBuilder, ')');
}
// face
for (let i = 0; i < drawCount; ++i) {
const v = isGeoTexture ? i : indices![i];
if (i > 0) StringBuilder.writeSafe(indexBuilder, ',');
StringBuilder.writeInteger(indexBuilder, v);
}
// color
const faceIndicesByMaterial = new Map<number, number[]>();
for (let i = 0; i < drawCount; i += 3) {
const v = isGeoTexture ? i : indices![i];
const color = UsdzExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
let alpha = uAlpha;
if (dTransparency) {
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
alpha *= 1 - transparency;
}
this.addMaterial(color, alpha);
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
let faceIndices = faceIndicesByMaterial.get(materialKey);
if (faceIndices === undefined) {
faceIndices = [];
faceIndicesByMaterial.set(materialKey, faceIndices);
}
faceIndices.push(i / 3);
}
// If this mesh uses only one material, bind it to the material directly.
// Otherwise, use GeomSubsets to bind it to multiple materials.
let materialBinding: string;
if (faceIndicesByMaterial.size === 1) {
const materialKey = faceIndicesByMaterial.keys().next().value;
materialBinding = `rel material:binding = </material${materialKey}>`;
} else {
const geomSubsets: string[] = [];
faceIndicesByMaterial.forEach((faceIndices: number[], materialKey: number) => {
geomSubsets.push(`
def GeomSubset "g${materialKey}"
{
uniform token elementType = "face"
uniform token familyName = "materialBind"
int[] indices = [${faceIndices.join(',')}]
rel material:binding = </material${materialKey}>
}
`);
});
materialBinding = geomSubsets.join('');
}
this.meshes.push(`
def Mesh "mesh${this.meshes.length}"
{
int[] faceVertexCounts = [${new Array(drawCount / 3).fill(3).join(',')}]
int[] faceVertexIndices = [${StringBuilder.getString(indexBuilder)}]
point3f[] points = [${StringBuilder.getString(vertexBuilder)}]
normal3f[] primvars:normals = [${StringBuilder.getString(normalBuilder)}] (
interpolation = "vertex"
)
uniform token subdivisionScheme = "none"
${materialBinding}
}
`);
}
}
async getData(ctx: RuntimeContext) {
const header = `#usda 1.0
(
customLayerData = {
string creator = "Mol* ${PLUGIN_VERSION}"
}
metersPerUnit = 1
)
`;
const usda = [header, ...this.materials, ...this.meshes].join('');
const usdaData = new Uint8Array(usda.length);
asciiWrite(usdaData, usda);
const zipDataObj = {
['model.usda']: usdaData
};
return {
usdz: await zip(ctx, zipDataObj, true)
};
}
async getBlob(ctx: RuntimeContext) {
const { usdz } = await this.getData(ctx);
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
}
constructor(private style: Style, boundingBox: Box3D, radius: number) {
super();
const t = Mat4();
// scale the model so that it fits within 1 meter
Mat4.fromUniformScaling(t, Math.min(1 / (radius * 2), 1));
// translate the model so that it sits on the ground plane (y = 0)
Mat4.translate(t, t, Vec3.create(
-(boundingBox.min[0] + boundingBox.max[0]) / 2,
-boundingBox.min[1],
-(boundingBox.min[2] + boundingBox.max[2]) / 2
));
this.centerTransform = t;
}
}

View File

@@ -16,7 +16,7 @@ import { RepresentationContext, RepresentationParamsGetter, Representation } fro
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
import { VisualContext } from '../../../mol-repr/visual';
import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from './prop';
@@ -28,6 +28,7 @@ import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
import { Sphere3D } from '../../../mol-math/geometry';
import { bondLabel } from '../../../mol-theme/label';
import { getUnitKindsParam } from '../../../mol-repr/structure/params';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
//

View File

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

View File

@@ -129,7 +129,7 @@ namespace Canvas3DContext {
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const input = InputObserver.fromElement(canvas, { pixelScale });
const input = InputObserver.fromElement(canvas, { pixelScale, preventGestures: true });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
@@ -222,9 +222,11 @@ interface Canvas3D {
animate(): void
/**
* Pause animation loop and optionally any rendering
* @param noDraw pause any rendering
* @param noDraw pause any rendering (drawPaused = true)
*/
pause(noDraw?: boolean): void
/** Sets drawPaused = false without starting the built in animation loop */
resume(): void
identify(x: number, y: number): PickData | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId | undefined): Representation.Loci
@@ -305,7 +307,6 @@ namespace Canvas3D {
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
let drawPending = false;
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
@@ -373,9 +374,13 @@ namespace Canvas3D {
let didRender = false;
controls.update(currentTime);
const cameraChanged = camera.update();
const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
if (resized || force || cameraChanged || multiSampleChanged) {
const shouldRender = force || cameraChanged || resized || forceNextRender;
forceNextRender = false;
const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample);
if (shouldRender || multiSampleChanged) {
let cam: Camera | StereoCamera = camera;
if (p.camera.stereo.name === 'on') {
stereoCamera.update();
@@ -394,24 +399,20 @@ namespace Canvas3D {
return didRender;
}
let forceNextDraw = false;
let forceNextRender = false;
let forceDrawAfterAllCommited = false;
let currentTime = 0;
let drawPaused = false;
function draw(force?: boolean) {
if (drawPaused) return;
if (render(!!force || forceNextDraw) && notifyDidDraw) {
if (render(!!force) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
forceNextDraw = false;
drawPending = false;
}
function requestDraw(force?: boolean) {
if (drawPending) return;
drawPending = true;
forceNextDraw = !!force;
forceNextRender = forceNextRender || !!force;
}
let animationFrameHandle = 0;
@@ -703,6 +704,7 @@ namespace Canvas3D {
animate,
resetTime,
pause,
resume: () => { drawPaused = false; },
identify,
mark,
getLoci,
@@ -816,6 +818,8 @@ namespace Canvas3D {
};
function updateViewport() {
const oldX = x, oldY = y, oldWidth = width, oldHeight = height;
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
@@ -836,6 +840,9 @@ namespace Canvas3D {
// console.log({ x, y, width, height });
}
if (oldX !== x || oldY !== y || oldWidth !== width || oldHeight !== height) {
forceNextRender = true;
}
}
function syncViewport() {

View File

@@ -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>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -10,7 +10,7 @@
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { Viewport } from '../camera/util';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
@@ -49,6 +49,9 @@ export const TrackballControlsParams = {
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
gestureScaleFactor: PD.Numeric(1, {}, { isHidden: true }),
maxWheelDelta: PD.Numeric(0.02, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
/**
@@ -91,6 +94,7 @@ namespace TrackballControls {
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
const wheelSub = input.wheel.subscribe(onWheel);
const pinchSub = input.pinch.subscribe(onPinch);
const gestureSub = input.gesture.subscribe(onGesture);
let _isInteracting = false;
@@ -390,25 +394,33 @@ namespace TrackballControls {
_isInteracting = false;
}
function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
function onWheel({ x, y, spinX, spinY, dz, buttons, modifiers }: WheelInput) {
if (outsideViewport(x, y)) return;
const delta = absMax(dx, dy, dz);
let delta = absMax(spinX * 0.075, spinY * 0.075, dz * 0.0001);
if (delta < -p.maxWheelDelta) delta = -p.maxWheelDelta;
else if (delta > p.maxWheelDelta) delta = p.maxWheelDelta;
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001;
_zoomEnd[1] += delta;
}
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
_focusEnd[1] += delta * 0.0001;
_focusEnd[1] += delta;
}
}
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
function onPinch({ fractionDelta, buttons, modifiers }: PinchInput) {
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_isInteracting = true;
_zoomEnd[1] += (fraction - 1) * 0.1;
_zoomEnd[1] += p.gestureScaleFactor * fractionDelta;
}
}
function onGesture({ deltaScale }: GestureInput) {
_isInteracting = true;
_zoomEnd[1] += p.gestureScaleFactor * deltaScale;
}
function dispose() {
if (disposed) return;
disposed = true;
@@ -416,6 +428,7 @@ namespace TrackballControls {
dragSub.unsubscribe();
wheelSub.unsubscribe();
pinchSub.unsubscribe();
gestureSub.unsubscribe();
interactionEndSub.unsubscribe();
}

View File

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

View File

@@ -16,7 +16,7 @@ export const start = Tuple.fst;
export const end = Tuple.snd;
export const min = Tuple.fst;
export function max(i: Tuple) { return Tuple.snd(i) - 1; }
export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
export const size = Tuple.diff;
export const hashCode = Tuple.hashCode;
export const toString = Tuple.toString;

View File

@@ -36,6 +36,12 @@ namespace IntTuple {
return _float64[0] as any;
}
/** snd - fst */
export function diff(t: IntTuple) {
_float64[0] = t as any;
return _int32[1] - _int32[0];
}
export function fst(t: IntTuple): number {
_float64[0] = t as any;
return _int32[0];

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2018 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.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -70,6 +70,15 @@ export function sortedCantorPairing(a: number, b: number) {
return a < b ? cantorPairing(a, b) : cantorPairing(b, a);
}
export function invertCantorPairing(out: [number, number], z: number) {
const w = Math.floor((Math.sqrt(8 * z + 1) - 1) / 2);
const t = (w * w + w) / 2;
const y = z - t;
out[0] = w - y;
out[1] = y;
return out;
}
/**
* 32 bit FNV-1a hash, see http://isthe.com/chongo/tech/comp/fnv/
*/

View File

@@ -21,7 +21,6 @@ const tmpCylinderCenter = Vec3();
const tmpCylinderMat = Mat4();
const tmpCylinderMatRot = Mat4();
const tmpCylinderScale = Vec3();
const tmpCylinderMatScale = Mat4();
const tmpCylinderStart = Vec3();
const tmpUp = Vec3();
@@ -31,9 +30,10 @@ 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);
Mat4.fromScaling(tmpCylinderMatScale, Vec3.set(tmpCylinderScale, 1, length, 1));
else Vec3.copy(tmpUp, up);
Vec3.set(tmpCylinderScale, 1, length, 1);
Vec3.makeRotation(tmpCylinderMatRot, tmpUp, tmpCylinderMatDir);
Mat4.mul(m, tmpCylinderMatRot, tmpCylinderMatScale);
Mat4.scale(m, tmpCylinderMatRot, tmpCylinderScale);
return Mat4.setTranslation(m, tmpCylinderCenter);
}

View File

@@ -8,13 +8,13 @@
import { ValueCell } from '../../../mol-util';
import { Vec3, Mat4, Mat3, Vec4 } from '../../../mol-math/linear-algebra';
import { Sphere3D } from '../../../mol-math/geometry';
import { transformPositionArray, transformDirectionArray, computeIndexedVertexNormals, GroupMapping, createGroupMapping} from '../../util';
import { transformPositionArray, transformDirectionArray, computeIndexedVertexNormals, GroupMapping, createGroupMapping } from '../../util';
import { GeometryUtils } from '../geometry';
import { createMarkers } from '../marker-data';
import { TransformData } from '../transform-data';
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
import { createColors } from '../color-data';
import { ChunkedArray, hashFnv32a } from '../../../mol-data/util';
import { ChunkedArray, hashFnv32a, invertCantorPairing, sortedCantorPairing } from '../../../mol-data/util';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { Theme } from '../../../mol-theme/theme';
@@ -25,6 +25,8 @@ import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { createEmptyClipping } from '../clipping-data';
import { RenderableState } from '../../../mol-gl/renderable';
import { arraySetAdd } from '../../../mol-util/array';
import { degToRad } from '../../../mol-math/misc';
export interface Mesh {
readonly kind: 'mesh',
@@ -332,10 +334,10 @@ export namespace Mesh {
mesh.vertexCount = newVertexCount;
mesh.triangleCount = newTriangleCount;
ValueCell.update(vertexBuffer, newVb) as ValueCell<Float32Array>;
ValueCell.update(groupBuffer, newGb) as ValueCell<Float32Array>;
ValueCell.update(indexBuffer, newIb) as ValueCell<Uint32Array>;
ValueCell.update(normalBuffer, newNb) as ValueCell<Float32Array>;
ValueCell.update(vertexBuffer, newVb);
ValueCell.update(groupBuffer, newGb);
ValueCell.update(indexBuffer, newIb);
ValueCell.update(normalBuffer, newNb);
// keep some original data, e.g., for geometry export
(mesh.meta.originalData as OriginalData) = { indexBuffer: ib, vertexCount, triangleCount };
@@ -345,6 +347,276 @@ export namespace Mesh {
//
function getNeighboursMap(mesh: Mesh) {
const { vertexCount, triangleCount } = mesh;
const elements = mesh.indexBuffer.ref.value;
const neighboursMap: number[][] = [];
for (let i = 0; i < vertexCount; ++i) {
neighboursMap[i] = [];
}
for (let i = 0; i < triangleCount; ++i) {
const v1 = elements[i * 3];
const v2 = elements[i * 3 + 1];
const v3 = elements[i * 3 + 2];
arraySetAdd(neighboursMap[v1], v2);
arraySetAdd(neighboursMap[v1], v3);
arraySetAdd(neighboursMap[v2], v1);
arraySetAdd(neighboursMap[v2], v3);
arraySetAdd(neighboursMap[v3], v1);
arraySetAdd(neighboursMap[v3], v2);
}
return neighboursMap;
}
function getEdgeCounts(mesh: Mesh) {
const { triangleCount } = mesh;
const elements = mesh.indexBuffer.ref.value;
const edgeCounts = new Map<number, number>();
const add = (a: number, b: number) => {
const z = sortedCantorPairing(a, b);
const c = edgeCounts.get(z) || 0;
edgeCounts.set(z, c + 1);
};
for (let i = 0; i < triangleCount; ++i) {
const a = elements[i * 3];
const b = elements[i * 3 + 1];
const c = elements[i * 3 + 2];
add(a, b); add(a, c); add(b, c);
}
return edgeCounts;
}
function getBorderVertices(edgeCounts: Map<number, number>) {
const borderVertices = new Set<number>();
const pair: [number, number] = [0, 0];
edgeCounts.forEach((c, z) => {
if (c === 1) {
invertCantorPairing(pair, z);
borderVertices.add(pair[0]);
borderVertices.add(pair[1]);
}
});
return borderVertices;
}
function getBorderNeighboursMap(neighboursMap: number[][], borderVertices: Set<number>, edgeCounts: Map<number, number>) {
const borderNeighboursMap = new Map<number, number[]>();
const add = (v: number, nb: number) => {
if (borderNeighboursMap.has(v)) arraySetAdd(borderNeighboursMap.get(v)!, nb);
else borderNeighboursMap.set(v, [nb]);
};
borderVertices.forEach(v => {
const neighbours = neighboursMap[v];
for (const nb of neighbours) {
if (borderVertices.has(nb) && edgeCounts.get(sortedCantorPairing(v, nb)) === 1) {
add(v, nb);
}
}
});
return borderNeighboursMap;
}
function trimEdges(mesh: Mesh, neighboursMap: number[][]) {
const { indexBuffer, triangleCount } = mesh;
const ib = indexBuffer.ref.value;
// new
const index = ChunkedArray.create(Uint32Array, 3, 1024, triangleCount);
let newTriangleCount = 0;
for (let i = 0; i < triangleCount; ++i) {
const a = ib[i * 3];
const b = ib[i * 3 + 1];
const c = ib[i * 3 + 2];
if (neighboursMap[a].length === 2 ||
neighboursMap[b].length === 2 ||
neighboursMap[c].length === 2) continue;
ChunkedArray.add3(index, a, b, c);
newTriangleCount += 1;
}
const newIb = ChunkedArray.compact(index);
mesh.triangleCount = newTriangleCount;
ValueCell.update(indexBuffer, newIb);
return mesh;
}
function fillEdges(mesh: Mesh, neighboursMap: number[][], borderNeighboursMap: Map<number, number[]>, maxLengthSquared: number) {
const { vertexBuffer, indexBuffer, normalBuffer, triangleCount } = mesh;
const vb = vertexBuffer.ref.value;
const ib = indexBuffer.ref.value;
const nb = normalBuffer.ref.value;
// new
const index = ChunkedArray.create(Uint32Array, 3, 1024, triangleCount);
let newTriangleCount = 0;
for (let i = 0; i < triangleCount; ++i) {
ChunkedArray.add3(index, ib[i * 3], ib[i * 3 + 1], ib[i * 3 + 2]);
newTriangleCount += 1;
}
const vA = Vec3();
const vB = Vec3();
const vC = Vec3();
const vD = Vec3();
const vAB = Vec3();
const vAC = Vec3();
const vAD = Vec3();
const vABC = Vec3();
const vAN = Vec3();
const vN = Vec3();
const AngleThreshold = degToRad(120);
const added = new Set<number>();
const indices = Array.from(borderNeighboursMap.keys())
.filter(v => borderNeighboursMap.get(v)!.length < 2)
.map(v => {
const bnd = borderNeighboursMap.get(v)!;
Vec3.fromArray(vA, vb, v * 3);
Vec3.fromArray(vB, vb, bnd[0] * 3);
Vec3.fromArray(vC, vb, bnd[1] * 3);
Vec3.sub(vAB, vB, vA);
Vec3.sub(vAC, vC, vA);
return [v, Vec3.angle(vAB, vAC)];
});
// start with the smallest angle
indices.sort(([, a], [, b]) => a - b);
for (const [v, angle] of indices) {
if (added.has(v) || angle > AngleThreshold) continue;
const nbs = borderNeighboursMap.get(v)!;
if (neighboursMap[nbs[0]].includes(nbs[1]) &&
!borderNeighboursMap.get(nbs[0])?.includes(nbs[1])
) continue;
Vec3.fromArray(vA, vb, v * 3);
Vec3.fromArray(vB, vb, nbs[0] * 3);
Vec3.fromArray(vC, vb, nbs[1] * 3);
Vec3.sub(vAB, vB, vA);
Vec3.sub(vAC, vC, vA);
Vec3.add(vABC, vAB, vAC);
if (Vec3.squaredDistance(vA, vB) >= maxLengthSquared) continue;
let add = false;
for (const nb of neighboursMap[v]) {
if (nbs.includes(nb)) continue;
Vec3.fromArray(vD, vb, nb * 3);
Vec3.sub(vAD, vD, vA);
if (Vec3.dot(vABC, vAD) < 0) {
add = true;
break;
}
}
if (!add) continue;
Vec3.fromArray(vAN, nb, v * 3);
Vec3.triangleNormal(vN, vA, vB, vC);
if (Vec3.dot(vN, vAN) > 0) {
ChunkedArray.add3(index, v, nbs[0], nbs[1]);
} else {
ChunkedArray.add3(index, nbs[1], nbs[0], v);
}
added.add(v); added.add(nbs[0]); added.add(nbs[1]);
newTriangleCount += 1;
}
const newIb = ChunkedArray.compact(index);
mesh.triangleCount = newTriangleCount;
ValueCell.update(indexBuffer, newIb);
return mesh;
}
function laplacianEdgeSmoothing(mesh: Mesh, borderNeighboursMap: Map<number, number[]>, options: { iterations: number, lambda: number }) {
const { iterations, lambda } = options;
const a = Vec3();
const b = Vec3();
const c = Vec3();
const t = Vec3();
const mu = -lambda;
let dst = new Float32Array(mesh.vertexBuffer.ref.value.length);
const step = (f: number) => {
const pos = mesh.vertexBuffer.ref.value;
dst.set(pos);
borderNeighboursMap.forEach((nbs, v) => {
if (nbs.length !== 2) return;
Vec3.fromArray(a, pos, v * 3);
Vec3.fromArray(b, pos, nbs[0] * 3);
Vec3.fromArray(c, pos, nbs[1] * 3);
const wab = 1 / Vec3.distance(a, b);
const wac = 1 / Vec3.distance(a, c);
Vec3.scale(b, b, wab);
Vec3.scale(c, c, wac);
Vec3.add(t, b, c);
Vec3.scale(t, t, 1 / (wab + wac));
Vec3.sub(t, t, a);
Vec3.scale(t, t, f);
Vec3.add(t, a, t);
Vec3.toArray(t, dst, v * 3);
});
const tmp = mesh.vertexBuffer.ref.value;
ValueCell.update(mesh.vertexBuffer, dst);
dst = tmp;
};
for (let k = 0; k < iterations; ++k) {
step(lambda);
step(mu);
}
}
export function smoothEdges(mesh: Mesh, options: { iterations: number, maxNewEdgeLength: number }) {
trimEdges(mesh, getNeighboursMap(mesh));
for (let k = 0; k < 10; ++k) {
const oldTriangleCount = mesh.triangleCount;
const edgeCounts = getEdgeCounts(mesh);
const neighboursMap = getNeighboursMap(mesh);
const borderVertices = getBorderVertices(edgeCounts);
const borderNeighboursMap = getBorderNeighboursMap(neighboursMap, borderVertices, edgeCounts);
fillEdges(mesh, neighboursMap, borderNeighboursMap, options.maxNewEdgeLength * options.maxNewEdgeLength);
if (mesh.triangleCount === oldTriangleCount) break;
}
const edgeCounts = getEdgeCounts(mesh);
const neighboursMap = getNeighboursMap(mesh);
const borderVertices = getBorderVertices(edgeCounts);
const borderNeighboursMap = getBorderNeighboursMap(neighboursMap, borderVertices, edgeCounts);
laplacianEdgeSmoothing(mesh, borderNeighboursMap, { iterations: options.iterations, lambda: 0.5 });
return mesh;
}
//
export const Params = {
...BaseGeometry.Params,
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
@@ -387,9 +659,6 @@ export namespace Mesh {
function createValues(mesh: Mesh, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): MeshValues {
const { instanceCount, groupCount } = locationIt;
if (instanceCount !== transform.instanceCount.ref.value) {
throw new Error('instanceCount values in TransformData and LocationIterator differ');
}
const positionIt = createPositionIterator(mesh, transform);
const color = createColors(locationIt, positionIt, theme.color);

View File

@@ -163,9 +163,6 @@ export namespace Spheres {
function createValues(spheres: Spheres, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): SpheresValues {
const { instanceCount, groupCount } = locationIt;
if (instanceCount !== transform.instanceCount.ref.value) {
throw new Error('instanceCount values in TransformData and LocationIterator differ');
}
const positionIt = createPositionIterator(spheres, transform);
const color = createColors(locationIt, positionIt, theme.color);

View File

@@ -206,9 +206,6 @@ export namespace Text {
function createValues(text: Text, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextValues {
const { instanceCount, groupCount } = locationIt;
if (instanceCount !== transform.instanceCount.ref.value) {
throw new Error('instanceCount values in TransformData and LocationIterator differ');
}
const positionIt = createPositionIterator(text, transform);
const color = createColors(locationIt, positionIt, theme.color);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -43,12 +43,13 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
if (transformData) {
ValueCell.update(transformData.matrix, transformData.matrix.ref.value);
ValueCell.update(transformData.transform, transformArray);
const transform = transformData.transform.ref.value.length >= instanceCount * 16 ? transformData.transform.ref.value : new Float32Array(instanceCount * 16);
transform.set(transformArray);
ValueCell.update(transformData.transform, transform);
ValueCell.updateIfChanged(transformData.uInstanceCount, instanceCount);
ValueCell.updateIfChanged(transformData.instanceCount, instanceCount);
const aTransform = transformData.aTransform.ref.value.length >= instanceCount * 16 ? transformData.aTransform.ref.value : new Float32Array(instanceCount * 16);
aTransform.set(transformArray);
ValueCell.update(transformData.aTransform, aTransform);
// Note that this sets `extraTransform` to identity transforms
@@ -59,14 +60,11 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
ValueCell.update(transformData.aInstance, fillSerial(aInstance, instanceCount));
ValueCell.update(transformData.hasReflection, hasReflection);
updateTransformData(transformData);
return transformData;
} else {
return {
aTransform: ValueCell.create(new Float32Array(transformArray)),
transformData = {
aTransform: ValueCell.create(new Float32Array(instanceCount * 16)),
matrix: ValueCell.create(Mat4.identity()),
transform: ValueCell.create(transformArray),
transform: ValueCell.create(new Float32Array(transformArray)),
extraTransform: ValueCell.create(fillIdentityTransform(new Float32Array(instanceCount * 16), instanceCount)),
uInstanceCount: ValueCell.create(instanceCount),
instanceCount: ValueCell.create(instanceCount),
@@ -74,6 +72,9 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
hasReflection: ValueCell.create(hasReflection),
};
}
updateTransformData(transformData);
return transformData;
}
const identityTransform = new Float32Array(16);

View File

@@ -64,21 +64,19 @@ function createHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Te
}
type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
let textureFramebuffer = LevelTexturesFramebuffers[level];
const size = Math.pow(2, level);
if (textureFramebuffer === undefined) {
const texture = ctx.isWebGL2
? getTexture(`level${level}`, ctx, 'image-int32', 'alpha', 'int', 'nearest')
: getTexture(`level${level}`, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
texture.define(size, size);
const framebuffer = getFramebuffer(`level${level}`, ctx);
const name = `level${level}`;
const texture = ctx.isWebGL2
? getTexture(name, ctx, 'image-int32', 'alpha', 'int', 'nearest')
: getTexture(name, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
texture.define(size, size);
let framebuffer = tryGetFramebuffer(name, ctx);
if (!framebuffer) {
framebuffer = getFramebuffer(name, ctx);
texture.attachFramebuffer(framebuffer, 0);
textureFramebuffer = { texture, framebuffer };
LevelTexturesFramebuffers[level] = textureFramebuffer;
}
return textureFramebuffer;
return { texture, framebuffer };
}
function setRenderingDefaults(ctx: WebGLContext) {
@@ -108,6 +106,11 @@ function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format
return webgl.namedTextures[_name];
}
function tryGetFramebuffer(name: string, webgl: WebGLContext): Framebuffer | undefined {
const _name = `${HistogramPyramidName}-${name}`;
return webgl.namedFramebuffers[_name];
}
export interface HistogramPyramid {
pyramidTex: Texture
count: number

View File

@@ -6,11 +6,12 @@
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Texture } from '../../mol-gl/webgl/texture';
import { printTextureImage } from '../../mol-gl/renderable/util';
import { PrintImageOptions, printTextureImage } from '../../mol-gl/renderable/util';
import { defaults, ValueCell } from '../../mol-util';
import { ValueSpec, AttributeSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { Vec2 } from '../../mol-math/linear-algebra';
import { GLRenderingContext } from '../../mol-gl/webgl/compat';
import { PixelData } from '../../mol-util/image';
export const QuadPositions = new Float32Array([
1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // First triangle
@@ -41,7 +42,7 @@ function getArrayForTexture(gl: GLRenderingContext, texture: Texture, size: numb
throw new Error('unknown/unsupported texture type');
}
export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number) {
export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number): PixelData {
const { gl, resources } = ctx;
width = defaults(width, texture.getWidth());
height = defaults(height, texture.getHeight());
@@ -55,6 +56,8 @@ export function readTexture(ctx: WebGLContext, texture: Texture, width?: number,
return { array, width, height };
}
export function printTexture(ctx: WebGLContext, texture: Texture, scale: number) {
printTextureImage(readTexture(ctx, texture), scale);
export function printTexture(ctx: WebGLContext, texture: Texture, options: Partial<PrintImageOptions> = {}) {
const pixelData = readTexture(ctx, texture);
PixelData.flipY(pixelData);
printTextureImage(pixelData, options);
}

View File

@@ -39,7 +39,15 @@ export function createTextureImage<T extends Uint8Array | Float32Array>(n: numbe
return { array, width, height };
}
export function printTextureImage(textureImage: TextureImage<any>, scale = 1) {
const DefaultPrintImageOptions = {
scale: 1,
pixelated: false,
id: 'molstar.debug.image'
};
export type PrintImageOptions = typeof DefaultPrintImageOptions
export function printTextureImage(textureImage: TextureImage<any>, options: Partial<PrintImageOptions> = {}) {
const { array, width, height } = textureImage;
const itemSize = array.length / (width * height);
const data = new Uint8ClampedArray(width * height * 4);
@@ -54,32 +62,50 @@ export function printTextureImage(textureImage: TextureImage<any>, scale = 1) {
} else {
console.warn(`itemSize '${itemSize}' not supported`);
}
return printImageData(new ImageData(data, width, height), scale);
return printImageData(new ImageData(data, width, height), options);
}
export function printImageData(imageData: ImageData, scale = 1, pixelated = false) {
const canvas = document.createElement('canvas');
let tmpCanvas: HTMLCanvasElement;
let tmpCanvasCtx: CanvasRenderingContext2D;
let tmpContainer: HTMLDivElement;
export function printImageData(imageData: ImageData, options: Partial<PrintImageOptions> = {}) {
const o = { ...DefaultPrintImageOptions, ...options };
const canvas = tmpCanvas || document.createElement('canvas');
tmpCanvas = canvas;
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext('2d');
const ctx = tmpCanvasCtx || canvas.getContext('2d');
tmpCanvasCtx = ctx;
if (!ctx) throw new Error('Could not create canvas 2d context');
ctx.putImageData(imageData, 0, 0);
if (!tmpContainer) {
tmpContainer = document.createElement('div');
tmpContainer.style.position = 'absolute';
tmpContainer.style.bottom = '0px';
tmpContainer.style.right = '0px';
tmpContainer.style.border = 'solid orange';
tmpContainer.style.pointerEvents = 'none';
document.body.appendChild(tmpContainer);
}
canvas.toBlob(imgBlob => {
const objectURL = window.URL.createObjectURL(imgBlob);
const img = document.createElement('img');
const objectURL = URL.createObjectURL(imgBlob);
const existingImg = document.getElementById(o.id) as HTMLImageElement;
const img = existingImg || document.createElement('img');
img.id = o.id;
img.src = objectURL;
img.style.width = imageData.width * scale + 'px';
img.style.height = imageData.height * scale + 'px';
if (pixelated) {
img.style.width = imageData.width * o.scale + 'px';
img.style.height = imageData.height * o.scale + 'px';
if (o.pixelated) {
// not supported in Firefox and IE
img.style.imageRendering = 'pixelated';
}
img.style.position = 'relative';
img.style.top = '0px';
img.style.left = '0px';
img.style.border = 'solid grey';
img.style.pointerEvents = 'none';
document.body.appendChild(img);
if (!existingImg) tmpContainer.appendChild(img);
}, 'image/png');
}

View File

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

View File

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

View File

@@ -22,8 +22,8 @@ M END
> <DATABASE_NAME>
drugbank
> <SMILES>
[O-]P([O-])([O-])=O
> 5225 <TEST_FIELD>
whatever
> <INCHI_IDENTIFIER>
InChI=1S/H3O4P/c1-5(2,3)4/h(H3,1,2,3,4)/p-3
@@ -362,22 +362,25 @@ describe('sdf reader', () => {
expect(bonds.atomIdxB.value(3)).toBe(5);
expect(bonds.order.value(3)).toBe(1);
expect(dataItems.dataHeader.value(0)).toBe('DATABASE_ID');
expect(dataItems.dataHeader.value(0)).toBe('<DATABASE_ID>');
expect(dataItems.data.value(0)).toBe('0');
expect(dataItems.dataHeader.value(1)).toBe('DATABASE_NAME');
expect(dataItems.dataHeader.value(1)).toBe('<DATABASE_NAME>');
expect(dataItems.data.value(1)).toBe('drugbank');
expect(dataItems.dataHeader.value(31)).toBe('SYNONYMS');
expect(dataItems.dataHeader.value(2)).toBe('5225 <TEST_FIELD>');
expect(dataItems.data.value(2)).toBe('whatever');
expect(dataItems.dataHeader.value(31)).toBe('<SYNONYMS>');
expect(dataItems.data.value(31)).toBe('Orthophosphate; Phosphate');
expect(compound1.dataItems.data.value(0)).toBe('0');
expect(compound2.dataItems.data.value(0)).toBe('1');
expect(compound3.dataItems.dataHeader.value(2)).toBe('PUBCHEM_CONFORMER_DIVERSEORDER');
expect(compound3.dataItems.dataHeader.value(2)).toBe('<PUBCHEM_CONFORMER_DIVERSEORDER>');
expect(compound3.dataItems.data.value(2)).toBe('1\n11\n10\n3\n15\n17\n13\n5\n16\n7\n14\n9\n8\n4\n18\n6\n12\n2');
expect(compound3.dataItems.dataHeader.value(21)).toBe('PUBCHEM_COORDINATE_TYPE');
expect(compound3.dataItems.dataHeader.value(21)).toBe('<PUBCHEM_COORDINATE_TYPE>');
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
});
});

View File

@@ -13,16 +13,20 @@ import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 */
export interface SdfFile {
readonly compounds: {
readonly molFile: MolFile,
readonly dataItems: {
readonly dataHeader: Column<string>,
readonly data: Column<string>
}
}[]
export interface SdfFileCompound {
readonly molFile: MolFile,
readonly dataItems: {
readonly dataHeader: Column<string>,
readonly data: Column<string>
}
}
export interface SdfFile {
readonly compounds: SdfFileCompound[]
}
const delimiter = '$$$$';
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
@@ -33,8 +37,8 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
if (line.startsWith(delimiter)) break;
if (!line) continue;
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
if (line.startsWith('> ')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 2, tokenizer.tokenEnd);
Tokenizer.markLine(tokenizer);
const start = tokenizer.tokenStart;
@@ -42,7 +46,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
let added = false;
while (tokenizer.position < tokenizer.length) {
const line2 = Tokenizer.readLine(tokenizer);
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> <')) {
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> ')) {
TokenBuilder.add(data, start, end);
added = true;
break;

View File

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

View File

@@ -13,21 +13,26 @@ import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-e
type Component = Table.Row<Pick<mmCIF_chemComp_schema, 'id' | 'name' | 'type'>>
const ProteinAtomIdsList = [
new Set([ 'CA' ]),
new Set([ 'C' ]),
new Set([ 'N' ])
new Set(['CA']),
new Set(['C']),
new Set(['N'])
];
const RnaAtomIdsList = [
new Set([ 'P', 'O3\'', 'O3*' ]),
new Set([ 'C4\'', 'C4*' ]),
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
new Set(['P', 'O3\'', 'O3*']),
new Set(['C4\'', 'C4*']),
new Set(['O2\'', 'O2*', 'F2\'', 'F2*'])
];
const DnaAtomIdsList = [
new Set([ 'P', 'O3\'', 'O3*' ]),
new Set([ 'C3\'', 'C3*' ]),
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
new Set(['P', 'O3\'', 'O3*']),
new Set(['C3\'', 'C3*']),
new Set(['O2\'', 'O2*', 'F2\'', 'F2*'])
];
/** Used to reduce false positives for atom name-based type guessing */
const NonPolymerNames = new Set([
'FMN', 'NCN', 'FNS', 'FMA' // Mononucleotides
]);
const StandardComponents = (function() {
const map = new Map<string, Component>();
const components: Component[] = [
@@ -151,9 +156,11 @@ export class ComponentBuilder {
this.set(StandardComponents.get(compId)!);
} else if (WaterNames.has(compId)) {
this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
} else if (NonPolymerNames.has(compId)) {
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'non-polymer' });
} else {
const atomIds = this.getAtomIds(index);
if (CharmmIonComponents.has(compId) && atomIds.size === 1) {
if (atomIds.size === 1 && CharmmIonComponents.has(compId)) {
this.set(CharmmIonComponents.get(compId)!);
} else {
const type = this.getType(atomIds);

View File

@@ -11,6 +11,7 @@ import { Vec3 } from '../../mol-math/linear-algebra';
import { degToRad, halfPI } from '../../mol-math/misc';
import { Cell } from '../../mol-math/geometry/spacegroup/cell';
import { Mutable } from '../../mol-util/type-helpers';
import { EPSILON, equalEps } from '../../mol-math/linear-algebra/3d/common';
const charmmTimeUnitFactor = 20.45482949774598;
@@ -66,7 +67,12 @@ export function coordinatesFromDcd(dcdFile: DcdFile): Task<Coordinates> {
} else {
frame.cell = Cell.create(
Vec3.create(c[0], c[2], c[5]),
Vec3.create(degToRad(c[1]), degToRad(c[3]), degToRad(c[4]))
// interpret angles very close to 0 as 90 deg
Vec3.create(
degToRad(equalEps(c[1], 0, EPSILON) ? 90 : c[1]),
degToRad(equalEps(c[3], 0, EPSILON) ? 90 : c[3]),
degToRad(equalEps(c[4], 0, EPSILON) ? 90 : c[4])
)
);
}
}

View File

@@ -17,7 +17,7 @@ import { ModelFormat } from '../format';
import { IndexPairBonds } from './property/bonds/index-pair';
import { Trajectory } from '../../mol-model/structure';
async function getModels(mol: MolFile, ctx: RuntimeContext) {
export async function getMolModels(mol: MolFile, format: ModelFormat<any> | undefined, ctx: RuntimeContext) {
const { atoms, bonds } = mol;
const MOL = Column.ofConst('MOL', mol.atoms.count, Column.Schema.str);
@@ -61,7 +61,7 @@ async function getModels(mol: MolFile, ctx: RuntimeContext) {
atom_site
});
const models = await createModels(basics, MolFormat.create(mol), ctx);
const models = await createModels(basics, format ?? MolFormat.create(mol), ctx);
if (models.frameCount > 0) {
const indexA = Column.ofIntArray(Column.mapToArray(bonds.atomIdxA, x => x - 1, Int32Array));
@@ -91,5 +91,5 @@ namespace MolFormat {
}
export function trajectoryFromMol(mol: MolFile): Task<Trajectory> {
return Task.create('Parse MOL', ctx => getModels(mol, ctx));
return Task.create('Parse MOL', ctx => getMolModels(mol, void 0, ctx));
}

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,15 +10,17 @@ import { Column } from '../../../../mol-data/db';
import { FormatPropertyProvider } from '../../common/property';
import { BondType } from '../../../../mol-model/structure/model/types';
import { ElementIndex } from '../../../../mol-model/structure';
import { DefaultBondMaxRadius } from '../../../../mol-model/structure/structure/unit/bonds/common';
export type IndexPairBondsProps = {
export type IndexPairsProps = {
readonly order: ArrayLike<number>
readonly distance: ArrayLike<number>
readonly flag: ArrayLike<BondType.Flag>
}
export type IndexPairBonds = IntAdjacencyGraph<ElementIndex, IndexPairBondsProps>
export type IndexPairs = IntAdjacencyGraph<ElementIndex, IndexPairsProps>
export type IndexPairBonds = { bonds: IndexPairs, maxDistance: number }
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairBondsProps>, count: number): IndexPairBonds {
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairsProps>, count: number): IndexPairs {
const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB);
const order = new Int8Array(builder.slotCount);
const distance = new Array(builder.slotCount);
@@ -51,13 +53,20 @@ export namespace IndexPairBonds {
count: number
}
export function fromData(data: Data) {
export const DefaultProps = { maxDistance: DefaultBondMaxRadius };
export type Props = typeof DefaultProps
export function fromData(data: Data, props: Partial<Props> = {}): IndexPairBonds {
const p = { ...DefaultProps, ...props };
const { pairs, count } = data;
const indexA = pairs.indexA.toArray() as ArrayLike<ElementIndex>;
const indexB = pairs.indexB.toArray() as ArrayLike<ElementIndex>;
const order = pairs.order && pairs.order.toArray();
const distance = pairs.distance && pairs.distance.toArray();
const flag = pairs.flag && pairs.flag.toArray();
return getGraph(indexA, indexB, { order, distance, flag }, count);
return {
bonds: getGraph(indexA, indexB, { order, distance, flag }, count),
maxDistance: p.maxDistance
};
}
}

View File

@@ -19,6 +19,7 @@ export { ModelSecondaryStructure };
type StructConf = Table<mmCIF_Schema['struct_conf']>
type StructSheetRange = Table<mmCIF_Schema['struct_sheet_range']>
type CoordinateType = 'label' | 'auth';
namespace ModelSecondaryStructure {
export const Descriptor: CustomPropertyDescriptor = {
@@ -30,9 +31,12 @@ namespace ModelSecondaryStructure {
export function fromStruct(conf: StructConf, sheetRange: StructSheetRange, hierarchy: AtomicHierarchy): SecondaryStructure {
const map: SecondaryStructureMap = new Map();
const elements: SecondaryStructure.Element[] = [{ kind: 'none' }];
addHelices(conf, map, elements);
const coordinates = getCoordinateType(conf, sheetRange);
addHelices(conf, coordinates, map, elements);
// must add Helices 1st because of 'key' value assignment.
addSheets(sheetRange, map, conf._rowCount, elements);
addSheets(sheetRange, coordinates, map, conf._rowCount, elements);
const n = hierarchy.residues._rowCount;
const getIndex = (rI: ResidueIndex) => rI;
@@ -43,15 +47,24 @@ namespace ModelSecondaryStructure {
elements
};
if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct);
if (map.size > 0) assignSecondaryStructureRanges(hierarchy, coordinates, map, secStruct);
return SecondaryStructure(secStruct.type, secStruct.key, secStruct.elements, getIndex);
}
}
function getCoordinateType(conf: StructConf, sheetRange: StructSheetRange): CoordinateType {
if (conf._rowCount > 0) {
if (conf.beg_label_seq_id.valueKind(0) !== Column.ValueKind.Present || conf.end_label_seq_id.valueKind(0) !== Column.ValueKind.Present) return 'auth';
} else if (sheetRange) {
if (sheetRange.beg_label_seq_id.valueKind(0) !== Column.ValueKind.Present || sheetRange.end_label_seq_id.valueKind(0) !== Column.ValueKind.Present) return 'auth';
}
return 'label';
}
type SecondaryStructureEntry = {
startSeqNumber: number,
startSeqId: number,
startInsCode: string | null,
endSeqNumber: number,
endSeqId: number,
endInsCode: string | null,
type: SecondaryStructureType,
key: number
@@ -59,13 +72,16 @@ type SecondaryStructureEntry = {
type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry[]>>
type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[] }
function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
function addHelices(cat: StructConf, coordinates: CoordinateType, map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
if (!cat._rowCount) return;
const { beg_label_asym_id, beg_auth_seq_id, pdbx_beg_PDB_ins_code } = cat;
const { end_auth_seq_id, pdbx_end_PDB_ins_code } = cat;
const { beg_label_asym_id, beg_label_seq_id, beg_auth_seq_id, pdbx_beg_PDB_ins_code } = cat;
const { end_label_seq_id, end_auth_seq_id, pdbx_end_PDB_ins_code } = cat;
const { pdbx_PDB_helix_class, conf_type_id, details } = cat;
const beg_seq_id = coordinates === 'label' ? beg_label_seq_id : beg_auth_seq_id;
const end_seq_id = coordinates === 'label' ? end_label_seq_id : end_auth_seq_id;
for (let i = 0, _i = cat._rowCount; i < _i; i++) {
const type = SecondaryStructureType.create(pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
? SecondaryStructureType.SecondaryStructurePdb[pdbx_PDB_helix_class.value(i)]
@@ -81,9 +97,9 @@ function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: Secon
details: details.valueKind(i) === Column.ValueKind.Present ? details.value(i) : void 0
};
const entry: SecondaryStructureEntry = {
startSeqNumber: beg_auth_seq_id.value(i),
startSeqId: beg_seq_id.value(i),
startInsCode: pdbx_beg_PDB_ins_code.value(i),
endSeqNumber: end_auth_seq_id.value(i),
endSeqId: end_seq_id.value(i),
endInsCode: pdbx_end_PDB_ins_code.value(i),
type,
key: elements.length
@@ -94,24 +110,27 @@ function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: Secon
const asymId = beg_label_asym_id.value(i)!;
if (map.has(asymId)) {
const entries = map.get(asymId)!;
if (entries.has(entry.startSeqNumber)) {
entries.get(entry.startSeqNumber)!.push(entry);
if (entries.has(entry.startSeqId)) {
entries.get(entry.startSeqId)!.push(entry);
} else {
entries.set(entry.startSeqNumber, [entry]);
entries.set(entry.startSeqId, [entry]);
}
} else {
map.set(asymId, new Map([[entry.startSeqNumber, [entry]]]));
map.set(asymId, new Map([[entry.startSeqId, [entry]]]));
}
}
}
function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) {
function addSheets(cat: StructSheetRange, coordinates: CoordinateType, map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) {
if (!cat._rowCount) return;
const { beg_label_asym_id, beg_auth_seq_id, pdbx_beg_PDB_ins_code } = cat;
const { end_auth_seq_id, pdbx_end_PDB_ins_code } = cat;
const { beg_label_asym_id, beg_label_seq_id, beg_auth_seq_id, pdbx_beg_PDB_ins_code } = cat;
const { end_label_seq_id, end_auth_seq_id, pdbx_end_PDB_ins_code } = cat;
const { sheet_id } = cat;
const beg_seq_id = coordinates === 'label' ? beg_label_seq_id : beg_auth_seq_id;
const end_seq_id = coordinates === 'label' ? end_label_seq_id : end_auth_seq_id;
const sheet_id_key = new Map<string, number>();
let currentKey = sheetCount + 1;
@@ -132,9 +151,9 @@ function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount
symmetry: void 0
};
const entry: SecondaryStructureEntry = {
startSeqNumber: beg_auth_seq_id.value(i),
startSeqId: beg_seq_id.value(i),
startInsCode: pdbx_beg_PDB_ins_code.value(i),
endSeqNumber: end_auth_seq_id.value(i),
endSeqId: end_seq_id.value(i),
endInsCode: pdbx_end_PDB_ins_code.value(i),
type,
key: elements.length
@@ -145,31 +164,33 @@ function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount
const asymId = beg_label_asym_id.value(i)!;
if (map.has(asymId)) {
const entries = map.get(asymId)!;
if (entries.has(entry.startSeqNumber)) {
entries.get(entry.startSeqNumber)!.push(entry);
if (entries.has(entry.startSeqId)) {
entries.get(entry.startSeqId)!.push(entry);
} else {
entries.set(entry.startSeqNumber, [entry]);
entries.set(entry.startSeqId, [entry]);
}
} else {
map.set(asymId, new Map([[entry.startSeqNumber, [entry]]]));
map.set(asymId, new Map([[entry.startSeqId, [entry]]]));
}
}
return;
}
function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: ResidueIndex, resEnd: ResidueIndex, data: SecondaryStructureData) {
const { auth_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
const { endSeqNumber, endInsCode, key, type } = entry;
function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, coordinates: CoordinateType, entry: SecondaryStructureEntry, resStart: ResidueIndex, resEnd: ResidueIndex, data: SecondaryStructureData) {
const { auth_seq_id, label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
const { endSeqId, endInsCode, key, type } = entry;
const seq_id = coordinates === 'label' ? label_seq_id : auth_seq_id;
let rI = resStart;
while (rI < resEnd) {
const seqNumber = auth_seq_id.value(rI);
const seqNumber = seq_id.value(rI);
data.type[rI] = type;
data.key[rI] = key;
if ((seqNumber > endSeqNumber) ||
(seqNumber === endSeqNumber && pdbx_PDB_ins_code.value(rI) === endInsCode)) {
if ((seqNumber > endSeqId) ||
(seqNumber === endSeqId && pdbx_PDB_ins_code.value(rI) === endInsCode)) {
break;
}
@@ -177,10 +198,11 @@ function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: Second
}
}
function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: SecondaryStructureMap, data: SecondaryStructureData) {
function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, coordinates: CoordinateType, map: SecondaryStructureMap, data: SecondaryStructureData) {
const { count: chainCount } = hierarchy.chainAtomSegments;
const { label_asym_id } = hierarchy.chains;
const { auth_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
const { auth_seq_id, label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
const seq_id = coordinates === 'label' ? label_seq_id : auth_seq_id;
for (let cI = 0 as ChainIndex; cI < chainCount; cI++) {
const resStart = AtomicHierarchy.chainStartResidueIndex(hierarchy, cI), resEnd = AtomicHierarchy.chainEndResidueIndexExcl(hierarchy, cI);
@@ -189,13 +211,13 @@ function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: Seconda
const entries = map.get(asymId)!;
for (let rI = resStart; rI < resEnd; rI++) {
const seqNumber = auth_seq_id.value(rI);
if (entries.has(seqNumber)) {
const entryList = entries.get(seqNumber)!;
const seqId = seq_id.value(rI);
if (entries.has(seqId)) {
const entryList = entries.get(seqId)!;
for (const entry of entryList) {
const insCode = pdbx_PDB_ins_code.value(rI);
if (entry.startInsCode !== insCode) continue;
assignSecondaryStructureEntry(hierarchy, entry, rI, resEnd, data);
assignSecondaryStructureEntry(hierarchy, coordinates, entry, rI, resEnd, data);
}
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { SdfFileCompound } from '../../mol-io/reader/sdf/parser';
import { Trajectory } from '../../mol-model/structure';
import { Task } from '../../mol-task';
import { ModelFormat } from '../format';
import { getMolModels } from './mol';
export { SdfFormat };
type SdfFormat = ModelFormat<SdfFileCompound>
namespace SdfFormat {
export function is(x?: ModelFormat): x is SdfFormat {
return x?.kind === 'sdf';
}
export function create(mol: SdfFileCompound): SdfFormat {
return { kind: 'sdf', name: mol.molFile.title, data: mol };
}
}
export function trajectoryFromSdf(mol: SdfFileCompound): Task<Trajectory> {
return Task.create('Parse SDF', ctx => getMolModels(mol.molFile, SdfFormat.create(mol), ctx));
}

View File

@@ -61,6 +61,14 @@ export function guessElementSymbolString(str: string) {
) return str;
}
if (l === 3) { // three chars
if (str === 'SOD') return 'NA';
if (str === 'POT') return 'K';
if (str === 'CES') return 'CS';
if (str === 'CAL') return 'CA';
if (str === 'CLA') return 'CL';
}
const c = str[0];
if (c === 'C' || c === 'H' || c === 'N' || c === 'O' || c === 'P' || c === 'S') return c;

View File

@@ -0,0 +1,31 @@
/**
* 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 { Model } from '../../mol-model/structure';
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
import { CustomModelProperty } from '../common/custom-model-property';
import { calcHelixOrientation, HelixOrientation } from './helix-orientation/helix-orientation';
export const HelixOrientationParams = { };
export type HelixOrientationParams = typeof HelixOrientationParams
export type HelixOrientationProps = PD.Values<HelixOrientationParams>
export type HelixOrientationValue = HelixOrientation;
export const HelixOrientationProvider: CustomModelProperty.Provider<HelixOrientationParams, HelixOrientationValue> = CustomModelProperty.createProvider({
label: 'Helix Orientation',
descriptor: CustomPropertyDescriptor({
name: 'molstar_helix_orientation'
}),
type: 'dynamic',
defaultParams: {},
getParams: () => ({}),
isApplicable: (data: Model) => true,
obtain: async (ctx, data) => {
return { value: calcHelixOrientation(data) };
}
});

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ElementIndex } from '../../../mol-model/structure';
import { Segmentation } from '../../../mol-data/int/segmentation';
import { SortedRanges } from '../../../mol-data/int/sorted-ranges';
import { OrderedSet } from '../../../mol-data/int';
import { Model } from '../../../mol-model/structure/model';
import { Vec3 } from '../../../mol-math/linear-algebra';
export interface HelixOrientation {
centers: ArrayLike<number>
}
/** Usees same definition as GROMACS' helixorient */
export function calcHelixOrientation(model: Model): HelixOrientation {
const { x, y, z } = model.atomicConformation;
const { polymerType, traceElementIndex } = model.atomicHierarchy.derived.residue;
const n = polymerType.length;
const elements = OrderedSet.ofBounds(0, model.atomicConformation.atomId.rowCount) as OrderedSet<ElementIndex>;
const polymerIt = SortedRanges.transientSegments(model.atomicRanges.polymerRanges, elements);
const residueIt = Segmentation.transientSegments(model.atomicHierarchy.residueAtomSegments, elements);
const centers = new Float32Array(n * 3);
const axes = new Float32Array(n * 3);
let i = 0;
let j = -1;
let s = -1;
const a1 = Vec3();
const a2 = Vec3();
const a3 = Vec3();
const a4 = Vec3();
const r12 = Vec3();
const r23 = Vec3();
const r34 = Vec3();
const v1 = Vec3();
const v2 = Vec3();
const vt = Vec3();
const diff13 = Vec3();
const diff24 = Vec3();
const axis = Vec3();
const prevAxis = Vec3();
while (polymerIt.hasNext) {
const ps = polymerIt.move();
residueIt.setSegment(ps);
i = -1;
s = -1;
while (residueIt.hasNext) {
i += 1;
const { index } = residueIt.move();
if (i === 0) s = index;
j = (index - 2);
const j3 = j * 3;
Vec3.copy(a1, a2);
Vec3.copy(a2, a3);
Vec3.copy(a3, a4);
const eI = traceElementIndex[index];
Vec3.set(a4, x[eI], y[eI], z[eI]);
if (i < 3) continue;
Vec3.sub(r12, a2, a1);
Vec3.sub(r23, a3, a2);
Vec3.sub(r34, a4, a3);
Vec3.sub(diff13, r12, r23);
Vec3.sub(diff24, r23, r34);
Vec3.cross(axis, diff13, diff24);
Vec3.normalize(axis, axis);
Vec3.toArray(axis, axes, j3);
const tmp = Math.cos(Vec3.angle(diff13, diff24));
const diff13Length = Vec3.magnitude(diff13);
const diff24Length = Vec3.magnitude(diff24);
const r = (
Math.sqrt(diff24Length * diff13Length) /
// clamp, to avoid numerical instabilities for when
// angle between diff13 and diff24 is close to 0
Math.max(2.0, 2.0 * (1.0 - tmp))
);
Vec3.scale(v1, diff13, r / diff13Length);
Vec3.sub(v1, a2, v1);
Vec3.toArray(v1, centers, j3);
Vec3.scale(v2, diff24, r / diff24Length);
Vec3.sub(v2, a3, v2);
Vec3.toArray(v2, centers, j3 + 3);
Vec3.copy(prevAxis, axis);
}
// calc axis as dir of second and third center pos
// project first trace atom onto axis to get first center pos
const s3 = s * 3;
Vec3.fromArray(v1, centers, s3 + 3);
Vec3.fromArray(v2, centers, s3 + 6);
Vec3.normalize(axis, Vec3.sub(axis, v1, v2));
const sI = traceElementIndex[s];
Vec3.set(a1, x[sI], y[sI], z[sI]);
Vec3.copy(vt, a1);
Vec3.projectPointOnVector(vt, vt, axis, v1);
Vec3.toArray(vt, centers, s3);
// calc axis as dir of n-1 and n-2 center pos
// project last traceAtom onto axis to get last center pos
const e = j + 2;
const e3 = e * 3;
Vec3.fromArray(v1, centers, e3 - 3);
Vec3.fromArray(v2, centers, e3 - 6);
Vec3.normalize(axis, Vec3.sub(axis, v1, v2));
const eI = traceElementIndex[e];
Vec3.set(a1, x[eI], y[eI], z[eI]);Vec3.copy(vt, a1);
Vec3.projectPointOnVector(vt, vt, axis, v1);
Vec3.toArray(vt, centers, e3);
}
return {
centers
};
}

View File

@@ -15,12 +15,13 @@ import { VisualContext } from '../../../mol-repr/visual';
import { Theme } from '../../../mol-theme/theme';
import { InteractionsProvider } from '../interactions';
import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { Interactions } from '../interactions/interactions';
import { InteractionFlag } from '../interactions/common';
import { Sphere3D } from '../../../mol-math/geometry';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);

View File

@@ -231,6 +231,11 @@ namespace Loci {
? StructureElement.Loci.extendToWholeModels(loci)
: loci;
},
'operator': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToWholeOperators(loci)
: loci;
},
'structure': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? Structure.toStructureElementLoci(loci.structure)

View File

@@ -39,6 +39,18 @@ const ProteinOneLetterCodes: { [name: string]: AminoAlphabet } = {
'SEC': 'U', // as per IUPAC definition
'PYL': 'O', // as per IUPAC definition
// charmm ff
'HSD': 'H', 'HSE': 'H', 'HSP': 'H',
'LSN': 'K',
'ASPP': 'D',
'GLUP': 'E',
// amber ff
'HID': 'H', 'HIE': 'H', 'HIP': 'H',
'LYN': 'K',
'ASH': 'D',
'GLH': 'E',
};
const DnaOneLetterCodes: { [name: string]: NuclecicAlphabet } = {

View File

@@ -1,33 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
const elm1 = [ 'H', 'C', 'O', 'N', 'S', 'P' ];
const elm2 = [ 'NA', 'CL', 'FE' ];
function charAtIsNumber(str: string, index: number) {
const code = str.charCodeAt(index);
return code >= 48 && code <= 57;
}
export function guessElement(str: string) {
let at = str.trim().toUpperCase();
if (charAtIsNumber(at, 0)) at = at.substr(1);
// parse again to check for a second integer
if (charAtIsNumber(at, 0)) at = at.substr(1);
const n = at.length;
if (n === 0) return '';
if (n === 1) return at;
if (n === 2) {
if (elm2.indexOf(at) !== -1) return at;
if (elm1.indexOf(at[0]) !== -1) return at[0];
}
if (n >= 3) {
if (elm1.indexOf(at[0]) !== -1) return at[0];
}
return '';
}

View File

@@ -99,7 +99,7 @@ export const PolymerTypeAtomRoleId: { [k in PolymerType]: { [k in AtomRole]: Set
[PolymerType.Protein]: {
trace: new Set(['CA']),
directionFrom: new Set(['C']),
directionTo: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
directionTo: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT', 'OT1']),
backboneStart: new Set(['N']),
backboneEnd: new Set(['C']),
// CA1 is used e.g. in GFP chromophores
@@ -246,8 +246,13 @@ export const AminoAcidNamesL = new Set([
'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS',
'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL',
'UNK', // unknown amino acid from CCD
'MSE', 'SEP', 'TPO', 'PTR', 'PCA' // common
'MSE', 'SEP', 'TPO', 'PTR', 'PCA', // common from CCD
// charmm ff
'HSD', 'HSE', 'HSP', 'LSN', 'ASPP', 'GLUP',
// amber ff
'HID', 'HIE', 'HIP', 'LYN', 'ASH', 'GLH',
]);
export const AminoAcidNamesD = new Set([
'DAL', // D-ALANINE
@@ -586,7 +591,13 @@ export const MaxAsa = {
'THR': 163.0,
'TRP': 264.0,
'TYR': 255.0,
'VAL': 165.0
'VAL': 165.0,
// charmm ff
'HSD': 216.0, 'HSE': 216.0, 'HSP': 216.0,
// amber ff
'HID': 216.0, 'HIE': 216.0, 'HIP': 216.0, 'ASH': 187.0, 'GLH': 214.0,
};
export const DefaultMaxAsa = 121.0;
@@ -620,9 +631,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
@@ -680,6 +691,12 @@ export const ResidueHydrophobicity = {
'THR': [0.14, 0.25, 0.11],
'TRP': [-1.85, -2.09, -0.24],
'TYR': [-0.94, -0.71, 0.23],
'VAL': [0.07, -0.46, -0.53]
'VAL': [0.07, -0.46, -0.53],
// charmm ff
'HSD': [0.17, 0.11, -0.06], 'HSE': [0.17, 0.11, -0.06], 'HSP': [0.96, 2.33, 1.37],
// amber ff
'HID': [0.17, 0.11, -0.06], 'HIE': [0.17, 0.11, -0.06], 'HIP': [0.96, 2.33, 1.37],
};
export const DefaultResidueHydrophobicity = [0.00, 0.00, 0.00];

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 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.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -22,9 +22,10 @@ import { NumberArray } from '../../../../mol-util/type-helpers';
import { StructureProperties } from '../properties';
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
import { Boundary } from '../../../../mol-math/geometry/boundary';
import { IntTuple } from '../../../../mol-data/int/tuple';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const osSize = OrderedSet.size;
const itDiff = IntTuple.diff;
/** Represents multiple structure element index locations */
export interface Loci {
@@ -74,7 +75,15 @@ export namespace Loci {
export function size(loci: Loci) {
let s = 0;
for (const u of loci.elements) s += osSize(u.indices);
// inlined for max performance, crucial for marking large cellpack models
// `for (const u of loci.elements) s += OrderedSet.size(u.indices);`
for (const { indices } of loci.elements) {
if (typeof indices === 'number') {
s += itDiff(indices as IntTuple);
} else {
s += (indices as SortedArray).length;
}
}
return s;
}
@@ -488,6 +497,27 @@ export namespace Loci {
return Loci(loci.structure, elements);
}
export function extendToWholeOperators(loci: Loci): Loci {
const elements: Loci['elements'][0][] = [];
const operators = new Set<string>();
const { units } = loci.structure;
for (let i = 0, len = loci.elements.length; i < len; i++) {
const e = loci.elements[i];
operators.add(e.unit.conformation.operator.name);
}
for (let i = 0, il = units.length; i < il; ++i) {
const unit = units[i];
if (operators.has(unit.conformation.operator.name)) {
const indices = OrderedSet.ofBounds(0, unit.elements.length) as OrderedSet<UnitIndex>;
elements[elements.length] = { unit, indices };
}
}
return Loci(loci.structure, elements);
}
//
const boundaryHelper = new BoundaryHelper('98');

View File

@@ -42,6 +42,7 @@ type State = {
boundary?: Boundary,
lookup3d?: StructureLookup3D,
interUnitBonds?: InterUnitBonds,
dynamicBonds: boolean,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
@@ -193,8 +194,8 @@ class Structure {
}
/** The parent or itself in case this is the root */
get root() {
return this.state.parent || this;
get root(): Structure {
return this.state.parent ?? this;
}
/** The root/top-most parent or `undefined` in case this is the root */
@@ -231,10 +232,14 @@ class Structure {
get interUnitBonds() {
if (this.state.interUnitBonds) return this.state.interUnitBonds;
this.state.interUnitBonds = computeInterUnitBonds(this);
this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
return this.state.interUnitBonds;
}
get dynamicBonds() {
return this.state.dynamicBonds;
}
get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups;
this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
@@ -351,30 +356,41 @@ class Structure {
}
remapModel(m: Model) {
const { dynamicBonds, interUnitBonds } = this.state;
const units: Unit[] = [];
for (const ug of this.unitSymmetryGroups) {
const unit = ug.units[0].remapModel(m);
const unit = ug.units[0].remapModel(m, dynamicBonds);
units.push(unit);
for (let i = 1, il = ug.units.length; i < il; ++i) {
const u = ug.units[i];
units.push(u.remapModel(m, unit.props));
units.push(u.remapModel(m, dynamicBonds, unit.props));
}
}
return Structure.create(units, {
label: this.label,
interUnitBonds: this.state.interUnitBonds,
interUnitBonds: dynamicBonds ? undefined : interUnitBonds,
dynamicBonds
});
}
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;
if (this.parent) {
const p = this.parent.coordinateSystem.isIdentity ? this.parent : Structure.transform(this.parent, this.parent.coordinateSystem.inverse);
const s = this.coordinateSystem.isIdentity ? p : Structure.transform(p, this.coordinateSystem.matrix);
this._proxy = new Structure(s.units, s.unitMap, s.unitIndexMap, { ...s.state, dynamicBonds: this.dynamicBonds }, { child: this, target: this.parent });
} else {
this._proxy = this;
}
return this._proxy;
}
get child(): Structure | undefined {
@@ -395,6 +411,7 @@ class Structure {
// always assign to ensure object shape
this._child = asParent?.child;
this._target = asParent?.target;
this._proxy = undefined;
}
}
@@ -601,6 +618,11 @@ namespace Structure {
export interface Props {
parent?: Structure
interUnitBonds?: InterUnitBonds
/**
* Ensure bonds are recalculated upon model changes.
* Also enables calculation of inter-unit bonds in water molecules.
*/
dynamicBonds?: boolean,
coordinateSystem?: SymmetryOperator
label?: string
/** Master model for structures of a protein model and multiple ligand models */
@@ -680,6 +702,7 @@ namespace Structure {
polymerResidueCount: -1,
polymerGapCount: -1,
polymerUnitCount: -1,
dynamicBonds: false,
coordinateSystem: SymmetryOperator.Default,
label: ''
};
@@ -688,6 +711,9 @@ namespace Structure {
if (props.parent) state.parent = props.parent.parent || props.parent;
if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds;
if (props.dynamicBonds) state.dynamicBonds = props.dynamicBonds;
else if (props.parent) state.dynamicBonds = props.parent.dynamicBonds;
if (props.coordinateSystem) state.coordinateSystem = props.coordinateSystem;
else if (props.parent) state.coordinateSystem = props.parent.coordinateSystem;
@@ -735,12 +761,12 @@ namespace Structure {
* Generally, a single unit corresponds to a single chain, with the exception
* of consecutive "single atom chains" with same entity_id and same auth_asym_id.
*/
export function ofModel(model: Model): Structure {
export function ofModel(model: Model, props: Props = {}): Structure {
const chains = model.atomicHierarchy.chainAtomSegments;
const { index } = model.atomicHierarchy;
const { auth_asym_id } = model.atomicHierarchy.chains;
const { atomicChainOperatorMappinng } = model;
const builder = new StructureBuilder({ label: model.label });
const builder = new StructureBuilder({ label: model.label, ...props });
for (let c = 0 as ChainIndex; c < chains.count; c++) {
const operator = atomicChainOperatorMappinng.get(c) || SymmetryOperator.Default;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 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.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -27,7 +27,11 @@ namespace StructureSymmetry {
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
const assembler = Structure.Builder({ coordinateSystem, label: structure.label });
const assembler = Structure.Builder({
coordinateSystem,
label: structure.label,
dynamicBonds: structure.dynamicBonds
});
const queryCtx = new QueryContext(structure);
@@ -57,7 +61,11 @@ namespace StructureSymmetry {
if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
const modelCenter = Vec3();
const assembler = Structure.Builder({ label: structure.label, representativeModel: models[0] });
const assembler = Structure.Builder({
label: structure.label,
representativeModel: models[0],
dynamicBonds: structure.dynamicBonds
});
const queryCtx = new QueryContext(structure);
@@ -205,7 +213,10 @@ function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) {
}
function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) {
const assembler = Structure.Builder({ label: structure.label });
const assembler = Structure.Builder({
label: structure.label,
dynamicBonds: structure.dynamicBonds
});
const { units } = structure;
for (const oper of operators) {
for (const unit of units) {
@@ -263,7 +274,10 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
return `${unit.invariantId}|${oper.name}`;
}
const assembler = Structure.Builder({ label: structure.label });
const assembler = Structure.Builder({
label: structure.label,
dynamicBonds: structure.dynamicBonds
});
const { units } = structure;
const center = Vec3.zero();

View File

@@ -144,7 +144,7 @@ namespace Unit {
getChild(elements: StructureElement.Set): Unit,
applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
remapModel(model: Model): Unit,
remapModel(model: Model, dynamicBonds: boolean): Unit,
readonly boundary: Boundary
readonly lookup3d: Lookup3D<StructureElement.UnitIndex>
@@ -218,9 +218,9 @@ namespace Unit {
return new Atomic(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation, this.conformation.r), this.props);
}
remapModel(model: Model, props?: AtomicProperties) {
remapModel(model: Model, dynamicBonds: boolean, props?: AtomicProperties) {
if (!props) {
props = { ...this.props, bonds: tryRemapBonds(this, this.props.bonds, model) };
props = { ...this.props, bonds: dynamicBonds ? undefined : tryRemapBonds(this, this.props.bonds, model) };
if (!Unit.isSameConformation(this, model)) {
props.boundary = undefined;
props.lookup3d = undefined;
@@ -378,7 +378,7 @@ namespace Unit {
return createCoarse(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseConformation(), this.conformation.r), this.props);
}
remapModel(model: Model, props?: CoarseProperties): Unit.Spheres | Unit.Gaussians {
remapModel(model: Model, dynamicBonds: boolean, props?: CoarseProperties): Unit.Spheres | Unit.Gaussians {
const coarseConformation = this.getCoarseConformation();
const modelCoarseConformation = getCoarseConformation(this.kind, model);

View File

@@ -7,13 +7,18 @@
import { ElementSymbol } from '../../../model/types';
/** Default for atomic bonds */
export const DefaultBondMaxRadius = 4;
export interface BondComputationProps {
forceCompute: boolean
noCompute: boolean
maxRadius: number
}
export const DefaultBondComputationProps: BondComputationProps = {
forceCompute: false,
noCompute: false
noCompute: false,
maxRadius: DefaultBondMaxRadius,
};
// H,D,T are all mapped to H

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 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.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -21,8 +21,6 @@ import { StructConn } from '../../../../../mol-model-formats/structure/property/
import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
import { Model } from '../../../model';
const MAX_RADIUS = 4;
const tmpDistVecA = Vec3();
const tmpDistVecB = Vec3();
function getDistance(unitA: Unit.Atomic, indexA: ElementIndex, unitB: Unit.Atomic, indexB: ElementIndex) {
@@ -35,6 +33,8 @@ const _imageTransform = Mat4();
const _imageA = Vec3();
function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComputationProps, builder: InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>) {
const { maxRadius } = props;
const { elements: atomsA, residueIndex: residueIndexA } = unitA;
const { x: xA, y: yA, z: zA } = unitA.model.atomicConformation;
const { elements: atomsB, residueIndex: residueIndexB } = unitB;
@@ -62,7 +62,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const isNotIdentity = !Mat4.isIdentity(imageTransform);
const { center: bCenter, radius: bRadius } = unitB.boundary.sphere;
const testDistanceSq = (bRadius + MAX_RADIUS) * (bRadius + MAX_RADIUS);
const testDistanceSq = (bRadius + maxRadius) * (bRadius + maxRadius);
builder.startUnitPair(unitA.id, unitB.id);
@@ -73,19 +73,20 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
if (Vec3.squaredDistance(_imageA, bCenter) > testDistanceSq) continue;
if (!props.forceCompute && indexPairs) {
const { order, distance, flag } = indexPairs.edgeProps;
const { maxDistance } = indexPairs;
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
const srcA = sourceIndex.value(aI);
for (let i = indexPairs.offset[srcA], il = indexPairs.offset[srcA + 1]; i < il; ++i) {
const bI = invertedIndex![indexPairs.b[i]];
for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) {
const bI = invertedIndex![b[i]];
const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
if (type_symbolA.value(aI) === 'H' && type_symbolB.value(bI) === 'H') continue;
const d = distance[i];
// only allow inter-unit index-pair bonds when a distance is given
if (d !== -1 && equalEps(getDistance(unitA, aI, unitB, bI), d, 0.5)) {
const dist = getDistance(unitA, aI, unitB, bI);
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
}
}
@@ -102,7 +103,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
if (_bI < 0) continue;
// check if the bond is within MAX_RADIUS for this pair of units
if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue;
if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue;
builder.add(_aI, _bI, { order: se.order, flag: se.flags });
added = true;
@@ -116,7 +117,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const occA = occupancyA.value(aI);
const { lookup3d } = unitB;
const { indices, count, squaredDistances } = lookup3d.find(_imageA[0], _imageA[1], _imageA[2], MAX_RADIUS);
const { indices, count, squaredDistances } = lookup3d.find(_imageA[0], _imageA[1], _imageA[2], maxRadius);
if (count === 0) continue;
const aeI = getElementIdx(type_symbolA.value(aI));
@@ -177,35 +178,29 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
export interface InterBondComputationProps extends BondComputationProps {
validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
ignoreWater: boolean
}
const DefaultInterBondComputationProps = {
...DefaultBondComputationProps,
ignoreWater: true
};
function findBonds(structure: Structure, props: InterBondComputationProps) {
const builder = new InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>();
const hasIndexPairBonds = structure.models.some(m => IndexPairBonds.Provider.get(m));
if (props.noCompute || structure.isCoarseGrained) {
if (props.noCompute || (structure.isCoarseGrained && !hasIndexPairBonds)) {
// TODO add function that only adds bonds defined in structConn and avoids using
// structure.lookup and unit.lookup (expensive for large structure and not
// needed for archival files or files with an MD topology)
return new InterUnitBonds(builder.getMap());
}
const indexPairs = structure.models.length === 1 && IndexPairBonds.Provider.get(structure.model);
if (indexPairs) {
const { distance } = indexPairs.edgeProps;
let hasDistance = false;
for (let i = 0, il = distance.length; i < il; ++i) {
if (distance[i] !== -1) {
hasDistance = true;
break;
}
}
if (!hasDistance) return new InterUnitBonds(builder.getMap());
}
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder);
}, {
maxRadius: MAX_RADIUS,
maxRadius: props.maxRadius,
validUnit: (unit: Unit) => Unit.isAtomic(unit),
validUnitPair: (unitA: Unit, unitB: Unit) => props.validUnitPair(structure, unitA, unitB)
});
@@ -214,8 +209,9 @@ function findBonds(structure: Structure, props: InterBondComputationProps) {
}
function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondComputationProps>): InterUnitBonds {
const p = { ...DefaultInterBondComputationProps, ...props };
return findBonds(structure, {
...DefaultBondComputationProps,
...p,
validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;
@@ -223,7 +219,7 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
(!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Water) &&
(!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Water)
);
return Structure.validUnitPair(s, a, b) && notWater;
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater);
}),
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 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.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -49,7 +49,8 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const { elements: atoms } = unit;
const { type_symbol } = unit.model.atomicHierarchy.atoms;
const atomCount = unit.elements.length;
const { edgeProps } = indexPairs;
const { maxDistance } = indexPairs;
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
const { atomSourceIndex: sourceIndex } = unit.model.atomicHierarchy;
const { invertedIndex } = Model.getInvertedAtomSourceIndex(unit.model);
@@ -57,7 +58,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const atomA: StructureElement.UnitIndex[] = [];
const atomB: StructureElement.UnitIndex[] = [];
const flags: number[] = [];
const order: number[] = [];
const orders: number[] = [];
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
const aI = atoms[_aI];
@@ -65,29 +66,30 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const srcA = sourceIndex.value(aI);
for (let i = indexPairs.offset[srcA], il = indexPairs.offset[srcA + 1]; i < il; ++i) {
const bI = invertedIndex[indexPairs.b[i]];
for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) {
const bI = invertedIndex[b[i]];
if (aI >= bI) continue;
const _bI = SortedArray.indexOf(unit.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
if (isHa && type_symbol.value(bI) === 'H') continue;
const d = edgeProps.distance[i];
if (d === -1 || d === void 0 || equalEps(getDistance(unit, aI, bI), d, 0.5)) {
const d = distance[i];
const dist = getDistance(unit, aI, bI);
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;
order[order.length] = edgeProps.order[i];
flags[flags.length] = edgeProps.flag[i];
orders[order.length] = order[i];
flags[flags.length] = flag[i];
}
}
}
return getGraph(atomA, atomB, order, flags, atomCount, false);
return getGraph(atomA, atomB, orders, flags, atomCount, false);
}
function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds {
const MAX_RADIUS = 4;
const { maxRadius } = props;
const { x, y, z } = unit.model.atomicConformation;
const atomCount = unit.elements.length;
@@ -168,7 +170,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
const atomIdA = label_atom_id.value(aI);
const componentPairs = componentMap ? componentMap.get(atomIdA) : void 0;
const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], MAX_RADIUS);
const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], maxRadius);
const isHa = isHydrogen(aeI);
const thresholdA = getElementThreshold(aeI);
const altA = label_alt_id.value(aI);
@@ -245,7 +247,7 @@ function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputatio
return IntraUnitBonds.Empty;
}
if (!p.forceCompute && IndexPairBonds.Provider.get(unit.model)!) {
if (!p.forceCompute && IndexPairBonds.Provider.get(unit.model)) {
return findIndexPairBonds(unit);
} else {
return findBonds(unit, p);

View File

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

View File

@@ -6,12 +6,12 @@
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { SymmetryOperator } from '../../mol-math/geometry';
import { Sphere3D, SymmetryOperator } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Structure } from '../../mol-model/structure';
import { StructureUnitTransforms } from '../../mol-model/structure/structure/util/unit-transforms';
const _unwindMatrix = Mat4.zero();
const _unwindMatrix = Mat4();
export function unwindStructureAssembly(structure: Structure, unitTransforms: StructureUnitTransforms, t: number) {
for (let i = 0, _i = structure.units.length; i < _i; i++) {
const u = structure.units[i];
@@ -20,15 +20,14 @@ export function unwindStructureAssembly(structure: Structure, unitTransforms: St
}
}
const _centerVec = Vec3.zero(), _transVec = Vec3.zero(), _transMat = Mat4.zero();
export function explodeStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number) {
const boundary = structure.boundary.sphere;
const d = boundary.radius * t;
const _centerVec = Vec3(), _transVec = Vec3(), _transMat = Mat4();
export function explodeStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number, sphere: Sphere3D) {
const d = sphere.radius * t;
for (let i = 0, _i = structure.units.length; i < _i; i++) {
const u = structure.units[i];
Vec3.transformMat4(_centerVec, u.lookup3d.boundary.sphere.center, u.conformation.operator.matrix);
Vec3.sub(_transVec, _centerVec, boundary.center);
Vec3.sub(_transVec, _centerVec, sphere.center);
Vec3.setMagnitude(_transVec, _transVec, d);
Mat4.fromTranslation(_transMat, _transVec);

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -16,6 +16,11 @@ import { Assembly, Symmetry } from '../../mol-model/structure/model/properties/s
import { PluginStateObject as SO } from '../objects';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
const CommonStructureParams = {
dynamicBonds: PD.Optional(PD.Boolean(false, { description: 'Ensure bonds are recalculated upon model changes. Also enables calculation of inter-unit bonds in water molecules.' })),
};
type CommonStructureProps = PD.ValuesFor<typeof CommonStructureParams>
export namespace RootStructureDefinition {
export function getParams(model?: Model, defaultValue?: 'auto' | 'model' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
const symmetry = model && ModelSymmetry.Provider.get(model);
@@ -40,19 +45,22 @@ export namespace RootStructureDefinition {
}
const modes = {
auto: PD.EmptyGroup(),
model: PD.EmptyGroup(),
auto: PD.Group(CommonStructureParams),
model: PD.Group(CommonStructureParams),
assembly: PD.Group({
id: PD.Optional(model
? PD.Select(assemblyIds.length ? assemblyIds[0][0] : '', assemblyIds, { label: 'Asm Id', description: 'Assembly Id' })
: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }))
: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' })),
...CommonStructureParams
}, { isFlat: true }),
'symmetry-mates': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 1 })
radius: PD.Numeric(5, { min: 0, max: 50, step: 1 }),
...CommonStructureParams
}, { isFlat: true }),
'symmetry': PD.Group({
ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { step: 1 }, { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { step: 1 }, { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { step: 1 }, { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
...CommonStructureParams
}, { isFlat: true }),
'symmetry-assembly': PD.Group({
generators: PD.ObjectList({
@@ -65,7 +73,8 @@ export namespace RootStructureDefinition {
asymIds: PD.MultiSelect([] as string[], asymIdsOptions)
}, e => `${e.asymIds.length} asym ids, ${e.operators.length} operators`, {
defaultValue: [] as { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[]
})
}),
...CommonStructureParams
}, { isFlat: true })
};
@@ -99,7 +108,7 @@ export namespace RootStructureDefinition {
return true;
}
async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string, props?: CommonStructureProps) {
let asm: Assembly | undefined = void 0;
const symmetry = ModelSymmetry.Provider.get(model);
@@ -118,7 +127,7 @@ export namespace RootStructureDefinition {
}
}
const base = Structure.ofModel(model);
const base = Structure.ofModel(model, props);
if (!asm) {
const label = { label: 'Model', description: Structure.elementDescription(base) };
return new SO.Molecule.Structure(base, label);
@@ -126,56 +135,57 @@ export namespace RootStructureDefinition {
id = asm.id;
const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
const props = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, props);
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, objProps);
}
async function buildSymmetry(ctx: RuntimeContext, model: Model, ijkMin: Vec3, ijkMax: Vec3) {
const base = Structure.ofModel(model);
async function buildSymmetry(ctx: RuntimeContext, model: Model, ijkMin: Vec3, ijkMax: Vec3, props?: CommonStructureProps) {
const base = Structure.ofModel(model, props);
const s = await StructureSymmetry.buildSymmetryRange(base, ijkMin, ijkMax).runInContext(ctx);
const props = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, props);
const objProps = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, objProps);
}
async function buildSymmetryMates(ctx: RuntimeContext, model: Model, radius: number) {
const base = Structure.ofModel(model);
async function buildSymmetryMates(ctx: RuntimeContext, model: Model, radius: number, props?: CommonStructureProps) {
const base = Structure.ofModel(model, props);
const s = await StructureSymmetry.builderSymmetryMates(base, radius).runInContext(ctx);
const props = { label: `Symmetry Mates`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, props);
const objProps = { label: `Symmetry Mates`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, objProps);
}
async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry) {
const base = Structure.ofModel(model);
async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry, props?: CommonStructureProps) {
const base = Structure.ofModel(model, props);
const s = await StructureSymmetry.buildSymmetryAssembly(base, generators, symmetry).runInContext(ctx);
const props = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, props);
const objProps = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, objProps);
}
export async function create(plugin: PluginContext, ctx: RuntimeContext, model: Model, params?: Params): Promise<SO.Molecule.Structure> {
const props = params?.params;
const symmetry = ModelSymmetry.Provider.get(model);
if (!symmetry || !params || params.name === 'model') {
const s = Structure.ofModel(model);
const s = Structure.ofModel(model, props);
return new SO.Molecule.Structure(s, { label: 'Model', description: Structure.elementDescription(s) });
}
if (params.name === 'auto') {
if (symmetry.assemblies.length === 0) {
const s = Structure.ofModel(model);
const s = Structure.ofModel(model, props);
return new SO.Molecule.Structure(s, { label: 'Model', description: Structure.elementDescription(s) });
} else {
return buildAssembly(plugin, ctx, model);
return buildAssembly(plugin, ctx, model, undefined, props);
}
}
if (params.name === 'assembly') {
return buildAssembly(plugin, ctx, model, params.params.id);
return buildAssembly(plugin, ctx, model, params.params.id, props);
}
if (params.name === 'symmetry') {
return buildSymmetry(ctx, model, params.params.ijkMin, params.params.ijkMax);
return buildSymmetry(ctx, model, params.params.ijkMin, params.params.ijkMax, props);
}
if (params.name === 'symmetry-mates') {
return buildSymmetryMates(ctx, model, params.params.radius);
return buildSymmetryMates(ctx, model, params.params.radius, props);
}
if (params.name === 'symmetry-assembly') {
return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry);
return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry, props);
}
throw new Error(`Unknown represetation type: ${(params as any).name}`);

View File

@@ -472,6 +472,21 @@ const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212
referencesCurrent: true
});
const surroundingAtoms = StructureSelectionQuery('Surrounding Atoms (5 \u212B) of Selection', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.modifier.includeSurroundings({
0: MS.internal.generator.current(),
radius: 5,
'as-whole-residues': false
}),
by: MS.internal.generator.current()
})
]), {
description: 'Select atoms within 5 \u212B of the current selection.',
category: StructureSelectionCategory.Manipulate,
referencesCurrent: true
});
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.generator.all(),
@@ -694,6 +709,7 @@ export const StructureSelectionQueries = {
aromaticRing,
surroundings,
surroundingLigands,
surroundingAtoms,
complement,
covalentlyBonded,
covalentlyOrMetallicBonded,

View File

@@ -40,6 +40,7 @@ import { coordinatesFromXtc } from '../../mol-model-formats/structure/xtc';
import { parseXyz } from '../../mol-io/reader/xyz/parser';
import { trajectoryFromXyz } from '../../mol-model-formats/structure/xyz';
import { parseSdf } from '../../mol-io/reader/sdf/parser';
import { trajectoryFromSdf } from '../../mol-model-formats/structure/sdf';
export { CoordinatesFromDcd };
export { CoordinatesFromXtc };
@@ -308,8 +309,8 @@ const TrajectoryFromSDF = PluginStateTransform.BuiltIn({
const models: Model[] = [];
for (const { molFile } of parsed.result.compounds) {
const traj = await trajectoryFromMol(molFile).runInContext(ctx);
for (const compound of parsed.result.compounds) {
const traj = await trajectoryFromSdf(compound).runInContext(ctx);
for (let i = 0; i < traj.frameCount; i++) {
models.push(await Task.resolveInContext(traj.getFrameAtIndex(i), ctx));
}

View File

@@ -236,23 +236,23 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
},
apply({ a, params }) {
const structure = a.data.sourceData;
const unitTransforms = new StructureUnitTransforms(structure.root);
explodeStructure(structure, unitTransforms, params.t);
const unitTransforms = new StructureUnitTransforms(structure);
explodeStructure(structure, unitTransforms, params.t, structure.root.boundary.sphere);
return new SO.Molecule.Structure.Representation3DState({
state: { unitTransforms },
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
info: structure.root,
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
info: structure,
repr: a.data.repr
}, { label: `Explode T = ${params.t.toFixed(2)}` });
},
update({ a, b, newParams, oldParams }) {
const structure = a.data.sourceData;
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
if (b.data.info !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
const unitTransforms = b.data.state.unitTransforms!;
explodeStructure(structure.root, unitTransforms, newParams.t);
explodeStructure(structure, unitTransforms, newParams.t, structure.root.boundary.sphere);
b.label = `Explode T = ${newParams.t.toFixed(2)}`;
b.data.repr = a.data.repr;
return StateTransformer.UpdateResult.Updated;
@@ -275,27 +275,27 @@ const SpinStructureRepresentation3D = PluginStateTransform.BuiltIn({
},
apply({ a, params }) {
const structure = a.data.sourceData;
const unitTransforms = new StructureUnitTransforms(structure.root);
const unitTransforms = new StructureUnitTransforms(structure);
const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, params);
spinStructure(structure, unitTransforms, params.t, axis, origin);
return new SO.Molecule.Structure.Representation3DState({
state: { unitTransforms },
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
info: structure.root,
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
info: structure,
repr: a.data.repr
}, { label: `Spin T = ${params.t.toFixed(2)}` });
},
update({ a, b, newParams, oldParams }) {
const structure = a.data.sourceData;
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
if (b.data.info !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
if (oldParams.t === newParams.t && oldParams.axis === newParams.axis && oldParams.origin === newParams.origin) return StateTransformer.UpdateResult.Unchanged;
const unitTransforms = b.data.state.unitTransforms!;
const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, newParams);
spinStructure(structure.root, unitTransforms, newParams.t, axis, origin);
spinStructure(structure, unitTransforms, newParams.t, axis, origin);
b.label = `Spin T = ${newParams.t.toFixed(2)}`;
b.data.repr = a.data.repr;
return StateTransformer.UpdateResult.Updated;

View File

@@ -41,6 +41,9 @@ export function MoleculeSvg() { return _Molecule; }
const _CubeOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></svg>;
export function CubeOutlineSvg() { return _CubeOutline; }
const _CubeScan = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M17,22V20H20V17H22V20.5C22,20.89 21.84,21.24 21.54,21.54C21.24,21.84 20.89,22 20.5,22H17M7,22H3.5C3.11,22 2.76,21.84 2.46,21.54C2.16,21.24 2,20.89 2,20.5V17H4V20H7V22M17,2H20.5C20.89,2 21.24,2.16 21.54,2.46C21.84,2.76 22,3.11 22,3.5V7H20V4H17V2M7,2V4H4V7H2V3.5C2,3.11 2.16,2.76 2.46,2.46C2.76,2.16 3.11,2 3.5,2H7M13,17.25L17,14.95V10.36L13,12.66V17.25M12,10.92L16,8.63L12,6.28L8,8.63L12,10.92M7,14.95L11,17.25V12.66L7,10.36V14.95M18.23,7.59C18.73,7.91 19,8.34 19,8.91V15.23C19,15.8 18.73,16.23 18.23,16.55L12.75,19.73C12.25,20.05 11.75,20.05 11.25,19.73L5.77,16.55C5.27,16.23 5,15.8 5,15.23V8.91C5,8.34 5.27,7.91 5.77,7.59L11.25,4.41C11.5,4.28 11.75,4.22 12,4.22C12.25,4.22 12.5,4.28 12.75,4.41L18.23,7.59Z" /></svg>;
export function CubeScanSvg() { return _CubeScan; }
const _CubeSend = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M16,4L9,8.04V15.96L16,20L23,15.96V8.04M16,6.31L19.8,8.5L16,10.69L12.21,8.5M0,7V9H7V7M11,10.11L15,12.42V17.11L11,14.81M21,10.11V14.81L17,17.11V12.42M2,11V13H7V11M4,15V17H7V15" /></svg>;
export function CubeSendSvg() { return _CubeSend; }

View File

@@ -31,18 +31,24 @@ export class PluginAnimationLoop {
this.plugin.canvas3d?.resetTime(t);
}
start() {
start(options?: { immediate?: boolean }) {
this.plugin.canvas3d?.resume();
this._isAnimating = true;
this.resetTime();
this.currentFrame = requestAnimationFrame(this.frame);
// TODO: should immediate be the default mode?
if (options?.immediate) this.frame();
else this.currentFrame = requestAnimationFrame(this.frame);
}
stop() {
stop(options?: { noDraw?: boolean }) {
this._isAnimating = false;
if (this.currentFrame !== void 0) {
cancelAnimationFrame(this.currentFrame);
this.currentFrame = void 0;
}
if (options?.noDraw) {
this.plugin.canvas3d?.pause(options?.noDraw);
}
}
constructor(private plugin: PluginContext) {

View File

@@ -76,8 +76,22 @@ export async function getContourLevelEmdb(plugin: PluginContext, taskCtx: Runtim
}
export async function getContourLevelPdbe(plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
// TODO: parametrize URL in plugin settings?
emdbId = emdbId.toUpperCase();
const header = await plugin.fetch({ url: `https://www.ebi.ac.uk/emdb/api/entry/map/${emdbId}`, type: 'json' }).runInContext(taskCtx);
const contours = header?.map?.contour_list?.contour;
if (!contours || contours.length === 0) {
// try fallback to the old API
return getContourLevelPdbeLegacy(plugin, taskCtx, emdbId);
}
return contours.find((c: any) => c.primary)?.level ?? contours[0].level;
}
async function getContourLevelPdbeLegacy(plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
// TODO: parametrize URL in plugin settings?
emdbId = emdbId.toUpperCase();
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const header = await plugin.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/emdb/entry/map/${emdbId}`, type: 'json' }).runInContext(taskCtx);
const emdbEntry = header?.[emdbId];
let contourLevel: number | undefined = void 0;

View File

@@ -14,7 +14,7 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
import { Theme } from '../../mol-theme/theme';
import { Task } from '../../mol-task';
import { PickingId } from '../../mol-geo/geometry/picking';
import { EmptyLoci, Loci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
import { EmptyLoci, Loci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
import { Overpaint } from '../../mol-theme/overpaint';
import { StructureParams } from './params';
@@ -77,6 +77,10 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
// Remap `loci` from equivalent structure to the current `_structure`
loci = Loci.remap(loci, _structure);
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
// Change to `EveryLoci` to allow for downstream optimizations
loci = EveryLoci;
}
} else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
return false;
}

View File

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

View File

@@ -22,6 +22,7 @@ export function getUnitKindsParam(defaultValue: UnitKind[]) {
export const StructureParams = {
unitKinds: getUnitKindsParam(['atomic', 'spheres']),
includeParent: PD.Boolean(false, { isHidden: true }),
};
export type StructureParams = typeof StructureParams

View File

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

View File

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

View File

@@ -27,6 +27,7 @@ export const BallAndStickParams = {
traceOnly: PD.Boolean(false, { isHidden: true }), // not useful here
...IntraUnitBondCylinderParams,
...InterUnitBondCylinderParams,
includeParent: PD.Boolean(false),
unitKinds: getUnitKindsParam(['atomic']),
sizeFactor: PD.Numeric(0.15, { min: 0.01, max: 10, step: 0.01 }),
sizeAspectRatio: PD.Numeric(2 / 3, { min: 0.01, max: 3, step: 0.01 }),

View File

@@ -1,5 +1,5 @@
/**
* 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>
*/
@@ -17,6 +17,7 @@ import { PolymerGapParams, PolymerGapVisual } from '../visual/polymer-gap-cylind
import { PolymerTraceParams, PolymerTraceVisual } from '../visual/polymer-trace-mesh';
import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { HelixOrientationProvider } from '../../../mol-model-props/computed/helix-orientation';
const CartoonVisuals = {
'polymer-trace': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerTraceParams>) => UnitsRepresentation('Polymer trace mesh', ctx, getParams, PolymerTraceVisual),
@@ -67,7 +68,17 @@ export const CartoonRepresentationProvider = StructureRepresentationProvider({
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.polymerResidueCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => SecondaryStructureProvider.attach(ctx, structure, void 0, true),
detach: (data) => SecondaryStructureProvider.ref(data, false)
attach: async (ctx: CustomProperty.Context, structure: Structure) => {
await SecondaryStructureProvider.attach(ctx, structure, void 0, true);
for (const m of structure.models) {
await HelixOrientationProvider.attach(ctx, m, void 0, true);
}
},
detach: (data) => {
SecondaryStructureProvider.ref(data, false);
for (const m of data.models) {
HelixOrientationProvider.ref(m, false);
}
}
}
});

View File

@@ -25,6 +25,7 @@ export const EllipsoidParams = {
...EllipsoidMeshParams,
...IntraUnitBondCylinderParams,
...InterUnitBondCylinderParams,
includeParent: PD.Boolean(false),
adjustCylinderLength: PD.Boolean(false, { isHidden: true }), // not useful here
unitKinds: getUnitKindsParam(['atomic']),
sizeFactor: PD.Numeric(1, { min: 0.01, max: 10, step: 0.01 }),

View File

@@ -23,6 +23,7 @@ const LineVisuals = {
export const LineParams = {
...IntraUnitBondLineParams,
...InterUnitBondLineParams,
includeParent: PD.Boolean(false),
sizeFactor: PD.Numeric(1.5, { min: 0.01, max: 10, step: 0.01 }),
unitKinds: getUnitKindsParam(['atomic']),
visuals: PD.MultiSelect(['intra-bond', 'inter-bond'], PD.objectToOptions(LineVisuals))

View File

@@ -8,7 +8,6 @@
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
import { Visual } from '../visual';
import { StructureGroup } from './units-visual';
import { RepresentationContext, RepresentationParamsGetter } from '../representation';
import { Structure, Unit, StructureElement, Bond } from '../../mol-model/structure';
import { Subject } from 'rxjs';
@@ -16,7 +15,7 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
import { Theme } from '../../mol-theme/theme';
import { Task } from '../../mol-task';
import { PickingId } from '../../mol-geo/geometry/picking';
import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
import { MarkerAction, MarkerActions, applyMarkerAction } from '../../mol-util/marker-action';
import { Overpaint } from '../../mol-theme/overpaint';
import { Transparency } from '../../mol-theme/transparency';
@@ -25,6 +24,7 @@ import { Interval } from '../../mol-data/int';
import { StructureParams } from './params';
import { Clipping } from '../../mol-theme/clipping';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { StructureGroup } from './visual/util/common';
export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { }
@@ -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.
@@ -196,6 +196,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
// Remap `loci` from equivalent structure to the current `_structure`
loci = Loci.remap(loci, _structure);
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
// Change to `EveryLoci` to allow for downstream optimizations
loci = EveryLoci;
}
} else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
return false;
}

View File

@@ -11,7 +11,7 @@ import { Visual, VisualContext } from '../visual';
import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
import { LocationIterator } from '../../mol-geo/util/location-iterator';
import { Theme } from '../../mol-theme/theme';
import { createUnitsTransform, includesUnitKind } from './visual/util/common';
import { createUnitsTransform, includesUnitKind, StructureGroup } from './visual/util/common';
import { createRenderObject, GraphicsRenderObject, RenderObjectValues } from '../../mol-gl/render-object';
import { PickingId } from '../../mol-geo/geometry/picking';
import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
@@ -41,13 +41,11 @@ import { Clipping } from '../../mol-theme/clipping';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { isPromiseLike } from '../../mol-util/type-helpers';
export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
function createUnitsRenderObject<G extends Geometry>(group: Unit.SymmetryGroup, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>, materialId: number) {
function createUnitsRenderObject<G extends Geometry>(structureGroup: StructureGroup, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<StructureParams & Geometry.Params<G>>, materialId: number) {
const { createValues, createRenderableState } = Geometry.getUtils(geometry);
const transform = createUnitsTransform(group);
const transform = createUnitsTransform(structureGroup, props.includeParent);
const values = createValues(geometry, transform, locationIt, theme, props);
const state = createRenderableState(props);
return createRenderObject(geometry.kind, values, state, materialId);
@@ -120,6 +118,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;
@@ -174,7 +177,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
if (updateState.createNew) {
locationIt = createLocationIterator(newStructureGroup);
if (newGeometry) {
renderObject = createUnitsRenderObject(newStructureGroup.group, newGeometry, locationIt, newTheme, newProps, materialId);
renderObject = createUnitsRenderObject(newStructureGroup, newGeometry, locationIt, newTheme, newProps, materialId);
positionIt = createPositionIterator(newGeometry, renderObject.values);
} else {
throw new Error('expected geometry to be given');
@@ -193,7 +196,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
if (updateState.updateMatrix) {
// console.log('update matrix');
createUnitsTransform(newStructureGroup.group, renderObject.values);
createUnitsTransform(newStructureGroup, newProps.includeParent, renderObject.values);
}
if (updateState.createGeometry) {

View File

@@ -1 +0,0 @@
// TODO

View File

@@ -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;
@@ -203,7 +203,7 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
createLocationIterator: BondIterator.fromStructure,
getLoci: getInterBondLoci,
eachLocation: eachInterBond,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>) => {
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
state.createGeometry = (
newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
newProps.linkScale !== currentProps.linkScale ||
@@ -218,6 +218,13 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
);
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
state.createGeometry = true;
state.updateTransform = true;
state.updateColor = true;
state.updateSize = true;
}
},
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
return !props.tryUseImpostor || !webgl;
@@ -232,7 +239,7 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
createLocationIterator: BondIterator.fromStructure,
getLoci: getInterBondLoci,
eachLocation: eachInterBond,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>) => {
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
@@ -249,6 +256,13 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
);
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
state.createGeometry = true;
state.updateTransform = true;
state.updateColor = true;
state.updateSize = true;
}
},
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
return props.tryUseImpostor && !!webgl;

View File

@@ -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];
@@ -118,7 +120,7 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
createLocationIterator: BondIterator.fromStructure,
getLoci: getInterBondLoci,
eachLocation: eachInterBond,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondLineParams>, currentProps: PD.Values<InterUnitBondLineParams>) => {
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondLineParams>, currentProps: PD.Values<InterUnitBondLineParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.linkScale !== currentProps.linkScale ||
@@ -128,6 +130,13 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
);
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
state.createGeometry = true;
state.updateTransform = true;
state.updateColor = true;
state.updateSize = true;
}
}
}, materialId);
}

View File

@@ -12,8 +12,8 @@ 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 { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup, UnitsCylindersParams, UnitsCylindersVisual } from '../units-visual';
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps, LinkStyle } from './util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsCylindersParams, UnitsCylindersVisual } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { BondType } from '../../../mol-model/structure/model/types';
import { BondCylinderParams, BondIterator, eachIntraBond, getIntraBondLoci, makeIntraBondIgnoreTest } from './util/bond';
@@ -22,16 +22,18 @@ 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';
import { StructureGroup } from './util/common';
// 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 +43,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 +71,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 +80,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 +125,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;
@@ -198,7 +223,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];
@@ -240,7 +266,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];

View File

@@ -10,14 +10,16 @@ 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 { UnitsVisual, UnitsLinesParams, UnitsLinesVisual, StructureGroup } from '../units-visual';
import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
import { UnitsVisual, UnitsLinesParams, UnitsLinesVisual } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { BondType } from '../../../mol-model/structure/model/types';
import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntraBondIgnoreTest } from './util/bond';
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';
import { StructureGroup } from './util/common';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const isBondType = BondType.is;
@@ -29,41 +31,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 +86,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 +145,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];

View File

@@ -6,12 +6,13 @@
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual } from '../units-visual';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { createElementSphereImpostor, ElementIterator, getElementLoci, eachElement, createElementSphereMesh } from './util/element';
import { VisualUpdateState } from '../../util';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
import { Structure } from '../../../mol-model/structure';
import { StructureGroup } from './util/common';
export const ElementSphereParams = {
...UnitsMeshParams,

View File

@@ -44,6 +44,7 @@ export const GaussianDensityVolumeParams = {
...ComplexDirectVolumeParams,
...GaussianDensityParams,
ignoreHydrogens: PD.Boolean(false),
includeParent: PD.Boolean(false, { isHidden: true }),
};
export type GaussianDensityVolumeParams = typeof GaussianDensityVolumeParams
@@ -99,6 +100,7 @@ export const UnitsGaussianDensityVolumeParams = {
...UnitsDirectVolumeParams,
...GaussianDensityParams,
ignoreHydrogens: PD.Boolean(false),
includeParent: PD.Boolean(false, { isHidden: true }),
};
export type UnitsGaussianDensityVolumeParams = typeof UnitsGaussianDensityVolumeParams

View File

@@ -5,7 +5,7 @@
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual, StructureGroup } from '../units-visual';
import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual } from '../units-visual';
import { GaussianDensityParams, computeUnitGaussianDensity, computeUnitGaussianDensityTexture2d, GaussianDensityProps, computeStructureGaussianDensity, computeStructureGaussianDensityTexture2d } from './util/gaussian';
import { VisualContext } from '../../visual';
import { Unit, Structure } from '../../../mol-model/structure';
@@ -18,7 +18,7 @@ import { TextureMesh } from '../../../mol-geo/geometry/texture-mesh/texture-mesh
import { extractIsosurface } from '../../../mol-gl/compute/marching-cubes/isosurface';
import { Sphere3D } from '../../../mol-math/geometry';
import { ComplexVisual, ComplexMeshParams, ComplexMeshVisual, ComplexTextureMeshVisual, ComplexTextureMeshParams } from '../complex-visual';
import { getUnitExtraRadius, getStructureExtraRadius, getVolumeSliceInfo } from './util/common';
import { getUnitExtraRadius, getStructureExtraRadius, getVolumeSliceInfo, StructureGroup } from './util/common';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { MeshValues } from '../../../mol-gl/renderable/mesh';
import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
@@ -30,6 +30,7 @@ const SharedParams = {
...ColorSmoothingParams,
ignoreHydrogens: PD.Boolean(false),
tryUseGpu: PD.Boolean(true),
includeParent: PD.Boolean(false, { isHidden: true }),
};
type SharedParams = typeof SharedParams
@@ -130,9 +131,9 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
},
processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
const csp = getColorSmoothingProps(props, theme, resolution);
if (csp) {
applyMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
(geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
}
},
@@ -190,9 +191,9 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
},
processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
const csp = getColorSmoothingProps(props, theme, resolution);
if (csp) {
applyMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
(geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
}
},
@@ -263,9 +264,9 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
},
processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
if (csp) {
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
const csp = getColorSmoothingProps(props, theme, resolution);
if (csp && webgl) {
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
(geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
}
},
@@ -339,9 +340,9 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
},
processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
if (csp) {
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
const csp = getColorSmoothingProps(props, theme, resolution);
if (csp && webgl) {
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
(geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
}
},

View File

@@ -42,6 +42,7 @@ export const GaussianWireframeParams = {
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
lineSizeAttenuation: PD.Boolean(false),
ignoreHydrogens: PD.Boolean(false),
includeParent: PD.Boolean(false, { isHidden: true }),
};
export type GaussianWireframeParams = typeof GaussianWireframeParams

View File

@@ -11,7 +11,7 @@ import { VisualContext } from '../../visual';
import { Unit, Structure } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molecular-surface';
import { computeUnitMolecularSurface } from './util/molecular-surface';
import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/algorithm';
import { ElementIterator, getElementLoci, eachElement } from './util/element';
import { VisualUpdateState } from '../../util';
@@ -29,6 +29,7 @@ export const MolecularSurfaceMeshParams = {
...ColorSmoothingParams,
};
export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams
export type MolecularSurfaceMeshProps = PD.Values<MolecularSurfaceMeshParams>
type MolecularSurfaceMeta = {
resolution?: number
@@ -37,7 +38,7 @@ type MolecularSurfaceMeta = {
//
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, mesh?: Mesh): Promise<Mesh> {
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> {
const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
const params = {
@@ -47,6 +48,11 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
};
const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
if (props.includeParent) {
const iterations = Math.ceil(2 / props.resolution);
Mesh.smoothEdges(surface, { iterations, maxNewEdgeLength: Math.sqrt(2) });
}
Mesh.transform(surface, transform);
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
@@ -71,6 +77,7 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
if (newProps.smoothColors.name !== currentProps.smoothColors.name) {
state.updateColor = true;
} else if (newProps.smoothColors.name === 'on' && currentProps.smoothColors.name === 'on') {
@@ -80,9 +87,9 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
},
processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta;
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
const csp = getColorSmoothingProps(props, theme, resolution);
if (csp) {
applyMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
(geometry.meta.colorTexture as MolecularSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
}
},

View File

@@ -5,7 +5,7 @@
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { VisualContext } from '../../../mol-repr/visual';
import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
@@ -22,6 +22,7 @@ import { UnitIndex } from '../../../mol-model/structure/structure/element/elemen
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { MoleculeType } from '../../../mol-model/structure/model/types';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
import { StructureGroup } from './util/common';
export const OrientationEllipsoidMeshParams = {
...UnitsMeshParams,

View File

@@ -1,34 +1,103 @@
/**
* 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 } 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';
import { StructureGroup } from './util/common';
// 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 +107,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 +144,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);
}

View File

@@ -1 +1,132 @@
// 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 } 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';
import { StructureGroup } from './util/common';
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);
}

View File

@@ -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>
*/
@@ -14,7 +14,7 @@ import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment,
import { isNucleic, SecondaryStructureType } from '../../../mol-model/structure/model/types';
import { addSheet } from '../../../mol-geo/geometry/mesh/builder/sheet';
import { addTube } from '../../../mol-geo/geometry/mesh/builder/tube';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
@@ -22,11 +22,13 @@ import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
import { Sphere3D } from '../../../mol-math/geometry';
import { StructureGroup } from './util/common';
export const PolymerTraceMeshParams = {
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }),
arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }),
tubularHelices: PD.Boolean(false),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo)
@@ -40,7 +42,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Mesh.createEmpty(mesh);
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor } = props;
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor, tubularHelices } = props;
const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2;
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh);
@@ -50,7 +52,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const { curvePoints, normalVectors, binormalVectors, widthValues, heightValues } = state;
let i = 0;
const polymerTraceIt = PolymerTraceIterator(unit, structure);
const polymerTraceIt = PolymerTraceIterator(unit, structure, { ignoreSecondaryStructure: false, useHelixOrientation: tubularHelices });
while (polymerTraceIt.hasNext) {
const v = polymerTraceIt.move();
builderState.currentGroup = i;
@@ -58,7 +60,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const isNucleicType = isNucleic(v.moleculeType);
const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta);
const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix);
const tension = isHelix ? HelixTension : StandardTension;
const tension = isHelix && !tubularHelices ? HelixTension : StandardTension;
const shift = isNucleicType ? NucleicShift : StandardShift;
interpolateCurveSegment(state, v, tension, shift);
@@ -112,9 +114,19 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
} else {
let h0: number, h1: number, h2: number;
if (isHelix && !v.isCoarseBackbone) {
h0 = w0 * aspectRatio;
h1 = w1 * aspectRatio;
h2 = w2 * aspectRatio;
if (tubularHelices) {
w0 *= aspectRatio * 1.5;
w1 *= aspectRatio * 1.5;
w2 *= aspectRatio * 1.5;
h0 = w0;
h1 = w1;
h2 = w2;
} else {
h0 = w0 * aspectRatio;
h1 = w1 * aspectRatio;
h2 = w2 * aspectRatio;
}
} else if (isNucleicType && !v.isCoarseBackbone) {
h0 = w0 * aspectRatio;
h1 = w1 * aspectRatio;
@@ -172,6 +184,7 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTraceParams>, currentProps: PD.Values<PolymerTraceParams>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.tubularHelices !== currentProps.tubularHelices ||
newProps.detail !== currentProps.detail ||
newProps.linearSegments !== currentProps.linearSegments ||
newProps.radialSegments !== currentProps.radialSegments ||

View File

@@ -46,7 +46,7 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
const { curvePoints, normalVectors, binormalVectors, widthValues, heightValues } = state;
let i = 0;
const polymerTraceIt = PolymerTraceIterator(unit, structure, true);
const polymerTraceIt = PolymerTraceIterator(unit, structure, { ignoreSecondaryStructure: true });
while (polymerTraceIt.hasNext) {
const v = polymerTraceIt.move();
builderState.currentGroup = i;

View File

@@ -8,18 +8,18 @@ import { BondType } from '../../../../mol-model/structure/model/types';
import { Unit, StructureElement, Structure, Bond } from '../../../../mol-model/structure';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { LocationIterator } from '../../../../mol-geo/util/location-iterator';
import { StructureGroup } from '../../units-visual';
import { LinkCylinderParams, LinkLineParams } from './link';
import { ObjectKeys } from '../../../../mol-util/type-helpers';
import { PickingId } from '../../../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../../../mol-model/loci';
import { Interval, OrderedSet, SortedArray } from '../../../../mol-data/int';
import { isH, isHydrogen } from './common';
import { isH, isHydrogen, StructureGroup } from './common';
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

View File

@@ -27,8 +27,8 @@ export const ColorSmoothingParams = {
};
export type ColorSmoothingParams = typeof ColorSmoothingParams
export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number, webgl?: WebGLContext) {
if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3 && webgl) {
export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number) {
if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3) {
let stride = 3;
if (props.smoothColors.name === 'on') {
resolution *= props.smoothColors.params.resolutionFactor;
@@ -39,7 +39,7 @@ export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, t
resolution = Math.max(0.5, resolution);
if (resolution > 1.2) stride = 2;
}
return { resolution, stride, webgl };
return { resolution, stride };
};
}

View File

@@ -71,7 +71,13 @@ export function getAltResidueLociFromId(structure: Structure, unit: Unit.Atomic,
//
export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
export function createUnitsTransform(structureGroup: StructureGroup, includeParent: boolean, transformData?: TransformData) {
const { child } = structureGroup.structure;
const units: ReadonlyArray<Unit> = includeParent && child
? structureGroup.group.units.filter(u => child.unitMap.has(u.id))
: structureGroup.group.units;
const unitCount = units.length;
const n = unitCount * 16;
const array = transformData && transformData.aTransform.ref.value.length >= n ? transformData.aTransform.ref.value : new Float32Array(n);

View File

@@ -17,10 +17,9 @@ import { PickingId } from '../../../../mol-geo/geometry/picking';
import { LocationIterator } from '../../../../mol-geo/util/location-iterator';
import { VisualContext } from '../../../../mol-repr/visual';
import { Theme } from '../../../../mol-theme/theme';
import { StructureGroup } from '../../../../mol-repr/structure/units-visual';
import { Spheres } from '../../../../mol-geo/geometry/spheres/spheres';
import { SpheresBuilder } from '../../../../mol-geo/geometry/spheres/spheres-builder';
import { isTrace, isH } from './common';
import { isTrace, isH, StructureGroup } from './common';
import { Sphere3D } from '../../../../mol-math/geometry';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
@@ -46,7 +45,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 (

View File

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

View File

@@ -9,8 +9,7 @@ import { Loci, EmptyLoci } from '../../../../mol-model/loci';
import { Interval } from '../../../../mol-data/int';
import { LocationIterator } from '../../../../mol-geo/util/location-iterator';
import { PickingId } from '../../../../mol-geo/geometry/picking';
import { StructureGroup } from '../../../../mol-repr/structure/units-visual';
import { getResidueLoci } from './common';
import { getResidueLoci, StructureGroup } from './common';
import { eachAtomicUnitTracedElement } from './polymer';
export namespace NucleotideLocationIterator {

View File

@@ -11,10 +11,9 @@ import { OrderedSet, Interval, SortedArray } from '../../../../mol-data/int';
import { EmptyLoci, Loci } from '../../../../mol-model/loci';
import { LocationIterator } from '../../../../mol-geo/util/location-iterator';
import { PickingId } from '../../../../mol-geo/geometry/picking';
import { StructureGroup } from '../../../structure/units-visual';
import { getResidueLoci } from './common';
import { getResidueLoci, StructureGroup } 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';

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