mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
599cfc1b1c | ||
|
|
84eccb5019 | ||
|
|
2dd07bb0e3 | ||
|
|
f404d23280 | ||
|
|
8b7333b470 | ||
|
|
7d26567d40 | ||
|
|
8f6dbf2192 | ||
|
|
db350ddfd3 | ||
|
|
c0144d826c | ||
|
|
de3e819b80 | ||
|
|
bbf96567b1 | ||
|
|
c9a3254bd6 | ||
|
|
bad6d030f1 | ||
|
|
e1ad67a059 | ||
|
|
8d3ac92989 | ||
|
|
d6eb334d12 | ||
|
|
c62f19623c | ||
|
|
77139afe7f | ||
|
|
54476ad85e | ||
|
|
6dd876232d | ||
|
|
ae2314d76c | ||
|
|
6667509745 | ||
|
|
5c871a5aae | ||
|
|
2482ef92af | ||
|
|
db59303a84 | ||
|
|
fe700953ff | ||
|
|
047946e41c | ||
|
|
f833efae37 | ||
|
|
be4b787e66 | ||
|
|
950b1c179a | ||
|
|
2bd1a01afb | ||
|
|
2fe43eda2b | ||
|
|
45fc0c61af | ||
|
|
7e7993f5ba | ||
|
|
9d34dbff0f | ||
|
|
ba1b03f01b | ||
|
|
791f7ca3c8 | ||
|
|
c14d50e4ff | ||
|
|
3e9de449c8 | ||
|
|
0132c7ef5e | ||
|
|
aa2222c086 | ||
|
|
fc2765d376 | ||
|
|
9d85194082 | ||
|
|
abfcc60898 | ||
|
|
c688a83fa2 | ||
|
|
77376056b9 | ||
|
|
8efd943c2b | ||
|
|
b230655439 | ||
|
|
8ba792c4b0 | ||
|
|
fde8ca69e4 | ||
|
|
9ee1439299 | ||
|
|
195668760e | ||
|
|
bd64f1db9a | ||
|
|
38a5a857aa | ||
|
|
5e8cdfe3a7 | ||
|
|
f892917e1c | ||
|
|
738b7f4ca5 | ||
|
|
bce53d03a5 | ||
|
|
74f721ab9f | ||
|
|
f011025f16 | ||
|
|
9e44cd83fa | ||
|
|
e0e45b64ac | ||
|
|
f10b152252 | ||
|
|
23cf5c2fdd | ||
|
|
e211abd5ae | ||
|
|
7199be4d62 | ||
|
|
e1a40ded1d | ||
|
|
8999c3097d | ||
|
|
44308fa1fd | ||
|
|
f5dd2f4579 | ||
|
|
104999b7dc | ||
|
|
e5341623d3 | ||
|
|
0e9238e5ec | ||
|
|
43c292e2df | ||
|
|
fbfd1b20d8 | ||
|
|
5330df87e1 | ||
|
|
ad6b3c6fe0 | ||
|
|
b983df7eb5 | ||
|
|
add76a87d9 | ||
|
|
f9f8350d28 | ||
|
|
b71c2f365c | ||
|
|
a5443189d3 | ||
|
|
7686b61728 | ||
|
|
844c13cd35 | ||
|
|
d1c8b92fdf | ||
|
|
93d33bca80 | ||
|
|
6550e53414 | ||
|
|
96dddb0998 | ||
|
|
baa64d8109 | ||
|
|
2df145aa8f | ||
|
|
06b9c5f2de | ||
|
|
e03b689f27 | ||
|
|
e4cdcff3ee | ||
|
|
f73150d074 | ||
|
|
451dc12689 | ||
|
|
a3fb7762d8 | ||
|
|
3dfafc3202 | ||
|
|
4fcea991d3 | ||
|
|
0607ed46d1 | ||
|
|
30d6244e82 | ||
|
|
fab8c74365 | ||
|
|
1952922e4e | ||
|
|
1eb351369e | ||
|
|
701d782485 | ||
|
|
dc8457c4dc | ||
|
|
f104cd4d11 | ||
|
|
9b56a6ae65 | ||
|
|
2485ad5a2f | ||
|
|
a56716ab6a | ||
|
|
e0aaaa989e | ||
|
|
9ec0f9e736 | ||
|
|
47968eeeec | ||
|
|
9c157b70e1 | ||
|
|
6d7e4ca227 | ||
|
|
fccd08d2ec | ||
|
|
19bae202d0 | ||
|
|
4ba0ae24e4 | ||
|
|
fcf3718d75 | ||
|
|
df1dd94f1c | ||
|
|
65ba401850 | ||
|
|
a98f5e1047 | ||
|
|
e5cf97d1ea | ||
|
|
1844fc14b2 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ tsconfig.commonjs.tsbuildinfo
|
||||
|
||||
*.sublime-workspace
|
||||
.idea
|
||||
|
||||
.DS_Store
|
||||
73
CHANGELOG.md
73
CHANGELOG.md
@@ -4,8 +4,71 @@ All notable changes to this project will be documented in this file, following t
|
||||
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
## [v2.3.0] - 2021-09-06
|
||||
|
||||
- Take include/exclude flags into account when displaying aromatic bonds
|
||||
- Improve marking performance
|
||||
- Avoid unnecessary draw calls/ui updates when marking
|
||||
- Check if loci is superset of visual
|
||||
- Check if loci overlaps with unit visual
|
||||
- Ensure ``Interval`` is used for ranges instead of ``SortedArray``
|
||||
- Add uniform marker type
|
||||
- Special case for reversing previous mark
|
||||
- Add optional marking pass
|
||||
- Outlines visible and hidden parts of highlighted/selected groups
|
||||
- Add highlightStrength/selectStrength renderer params
|
||||
|
||||
## [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
|
||||
|
||||
@@ -33,8 +96,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 +115,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
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.7",
|
||||
"version": "2.3.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.0.7",
|
||||
"version": "2.2.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.7",
|
||||
"version": "2.3.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -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' }),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
231
src/extensions/geo-export/usdz-exporter.ts
Normal file
231
src/extensions/geo-export/usdz-exporter.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
//
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import { StereoCamera, StereoCameraParams } from './camera/stereo';
|
||||
import { Helper } from './helper/helper';
|
||||
import { Passes } from './passes/passes';
|
||||
import { shallowEqual } from '../mol-util';
|
||||
import { MarkingParams } from './passes/marking';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
camera: PD.Group({
|
||||
@@ -80,6 +81,7 @@ export const Canvas3DParams = {
|
||||
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
marking: PD.Group(MarkingParams),
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
debug: PD.Group(DebugHelperParams),
|
||||
@@ -129,7 +131,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,11 +224,13 @@ 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
|
||||
mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void
|
||||
getLoci(pickingId: PickingId | undefined): Representation.Loci
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
@@ -305,7 +309,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;
|
||||
@@ -336,7 +339,7 @@ namespace Canvas3D {
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) {
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
@@ -346,7 +349,7 @@ namespace Canvas3D {
|
||||
changed = helper.camera.mark(loci, action) || changed;
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
if (changed && !noDraw) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
@@ -373,9 +376,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();
|
||||
@@ -385,7 +392,7 @@ namespace Canvas3D {
|
||||
if (MultiSamplePass.isEnabled(p.multiSample)) {
|
||||
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
|
||||
} else {
|
||||
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing);
|
||||
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking);
|
||||
}
|
||||
pickHelper.dirty = true;
|
||||
didRender = true;
|
||||
@@ -394,24 +401,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;
|
||||
@@ -635,6 +638,7 @@ namespace Canvas3D {
|
||||
viewport: p.viewport,
|
||||
|
||||
postprocessing: { ...p.postprocessing },
|
||||
marking: { ...p.marking },
|
||||
multiSample: { ...p.multiSample },
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
@@ -703,6 +707,7 @@ namespace Canvas3D {
|
||||
animate,
|
||||
resetTime,
|
||||
pause,
|
||||
resume: () => { drawPaused = false; },
|
||||
identify,
|
||||
mark,
|
||||
getLoci,
|
||||
@@ -769,6 +774,7 @@ namespace Canvas3D {
|
||||
}
|
||||
|
||||
if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing);
|
||||
if (props.marking) Object.assign(p.marking, props.marking);
|
||||
if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
|
||||
if (props.renderer) renderer.setProps(props.renderer);
|
||||
if (props.trackball) controls.setProps(props.trackball);
|
||||
@@ -816,6 +822,8 @@ namespace Canvas3D {
|
||||
};
|
||||
|
||||
function updateViewport() {
|
||||
const oldX = x, oldY = y, oldWidth = width, oldHeight = height;
|
||||
|
||||
if (p.viewport.name === 'canvas') {
|
||||
x = 0;
|
||||
y = 0;
|
||||
@@ -831,11 +839,11 @@ namespace Canvas3D {
|
||||
height = Math.round(p.viewport.params.height * gl.drawingBufferHeight);
|
||||
y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight);
|
||||
width = Math.round(p.viewport.params.width * gl.drawingBufferWidth);
|
||||
// if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x;
|
||||
// if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1;
|
||||
// console.log({ x, y, width, height });
|
||||
}
|
||||
|
||||
if (oldX !== x || oldY !== y || oldWidth !== width || oldHeight !== height) {
|
||||
forceNextRender = true;
|
||||
}
|
||||
}
|
||||
|
||||
function syncViewport() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { copy_frag } from '../../mol-gl/shader/copy.frag';
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
import { WboitPass } from './wboit';
|
||||
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
|
||||
import { MarkingPass, MarkingProps } from './marking';
|
||||
|
||||
const DepthMergeSchema = {
|
||||
...QuadSchema,
|
||||
@@ -92,6 +93,7 @@ export class DrawPass {
|
||||
private copyFboPostprocessing: CopyRenderable
|
||||
|
||||
private wboit: WboitPass | undefined
|
||||
private readonly marking: MarkingPass
|
||||
readonly postprocessing: PostprocessingPass
|
||||
private readonly antialiasing: AntialiasingPass
|
||||
|
||||
@@ -122,6 +124,7 @@ export class DrawPass {
|
||||
this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth);
|
||||
|
||||
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
|
||||
this.marking = new MarkingPass(webgl, width, height);
|
||||
this.postprocessing = new PostprocessingPass(webgl, this);
|
||||
this.antialiasing = new AntialiasingPass(webgl, this);
|
||||
|
||||
@@ -162,6 +165,7 @@ export class DrawPass {
|
||||
this.wboit.setSize(width, height);
|
||||
}
|
||||
|
||||
this.marking.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.antialiasing.setSize(width, height);
|
||||
}
|
||||
@@ -281,10 +285,11 @@ export class DrawPass {
|
||||
renderer.renderBlendedTransparent(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
|
||||
const volumeRendering = scene.volumes.renderables.length > 0;
|
||||
const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
|
||||
const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
|
||||
const markingEnabled = MarkingPass.isEnabled(markingProps);
|
||||
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
renderer.setViewport(x, y, width, height);
|
||||
@@ -309,6 +314,22 @@ export class DrawPass {
|
||||
this.drawTarget.bind();
|
||||
}
|
||||
|
||||
if (markingEnabled) {
|
||||
const markingDepthTest = markingProps.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest) {
|
||||
this.marking.depthTarget.bind();
|
||||
renderer.clear(false);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(markingProps);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
if (helper.debug.isEnabled) {
|
||||
helper.debug.syncVisibility();
|
||||
renderer.renderBlended(helper.debug.scene, camera, null);
|
||||
@@ -338,15 +359,15 @@ export class DrawPass {
|
||||
this.webgl.gl.flush();
|
||||
}
|
||||
|
||||
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
|
||||
renderer.setTransparentBackground(transparentBackground);
|
||||
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
} else {
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,13 @@ import { Viewport } from '../camera/util';
|
||||
import { PixelData } from '../../mol-util/image';
|
||||
import { Helper } from '../helper/helper';
|
||||
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
|
||||
import { MarkingParams } from './marking';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
marking: PD.Group(MarkingParams),
|
||||
|
||||
cameraHelper: PD.Group(CameraHelperParams),
|
||||
};
|
||||
@@ -85,7 +87,7 @@ export class ImagePass {
|
||||
this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
|
||||
this._colorTarget = this.multiSamplePass.colorTarget;
|
||||
} else {
|
||||
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing);
|
||||
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking);
|
||||
this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
|
||||
}
|
||||
}
|
||||
|
||||
194
src/mol-canvas3d/passes/marking.ts
Normal file
194
src/mol-canvas3d/passes/marking.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { quad_vert } from '../../mol-gl/shader/quad.vert';
|
||||
import { overlay_frag } from '../../mol-gl/shader/marking/overlay.frag';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
|
||||
|
||||
export const MarkingParams = {
|
||||
enabled: PD.Boolean(false),
|
||||
highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
|
||||
selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
|
||||
edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),
|
||||
ghostEdgeStrength: PD.Numeric(0.3, { min: 0, max: 1, step: 0.1 }, { description: 'Opacity of the hidden edges that are covered by other geometry. When set to 1, one less geometry render pass is done.' }),
|
||||
innerEdgeFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Factor to multiply the inner edge color with - for added contrast.' }),
|
||||
};
|
||||
export type MarkingProps = PD.Values<typeof MarkingParams>
|
||||
|
||||
export class MarkingPass {
|
||||
static isEnabled(props: MarkingProps) {
|
||||
return props.enabled;
|
||||
}
|
||||
|
||||
readonly depthTarget: RenderTarget
|
||||
readonly maskTarget: RenderTarget
|
||||
private readonly edgesTarget: RenderTarget
|
||||
|
||||
private readonly edge: EdgeRenderable
|
||||
private readonly overlay: OverlayRenderable
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
this.depthTarget = webgl.createRenderTarget(width, height);
|
||||
this.maskTarget = webgl.createRenderTarget(width, height);
|
||||
this.edgesTarget = webgl.createRenderTarget(width, height);
|
||||
|
||||
this.edge = getEdgeRenderable(webgl, this.maskTarget.texture);
|
||||
this.overlay = getOverlayRenderable(webgl, this.edgesTarget.texture);
|
||||
}
|
||||
|
||||
private setEdgeState(viewport: Viewport) {
|
||||
const { gl, state } = this.webgl;
|
||||
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.BLEND);
|
||||
state.blendFunc(gl.ONE, gl.ONE);
|
||||
state.blendEquation(gl.FUNC_ADD);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
const { x, y, width, height } = viewport;
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private setOverlayState(viewport: Viewport) {
|
||||
const { gl, state } = this.webgl;
|
||||
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.BLEND);
|
||||
state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
state.blendEquation(gl.FUNC_ADD);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
const { x, y, width, height } = viewport;
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const w = this.depthTarget.getWidth();
|
||||
const h = this.depthTarget.getHeight();
|
||||
|
||||
if (width !== w || height !== h) {
|
||||
this.depthTarget.setSize(width, height);
|
||||
this.maskTarget.setSize(width, height);
|
||||
this.edgesTarget.setSize(width, height);
|
||||
|
||||
ValueCell.update(this.edge.values.uTexSizeInv, Vec2.set(this.edge.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
|
||||
ValueCell.update(this.overlay.values.uTexSizeInv, Vec2.set(this.overlay.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
|
||||
}
|
||||
}
|
||||
|
||||
update(props: MarkingProps) {
|
||||
const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props;
|
||||
|
||||
const { values: edgeValues } = this.edge;
|
||||
const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio);
|
||||
if (edgeValues.dEdgeScale.ref.value !== _edgeScale) {
|
||||
ValueCell.update(edgeValues.dEdgeScale, _edgeScale);
|
||||
this.edge.update();
|
||||
}
|
||||
|
||||
const { values: overlayValues } = this.overlay;
|
||||
ValueCell.update(overlayValues.uHighlightEdgeColor, Color.toVec3Normalized(overlayValues.uHighlightEdgeColor.ref.value, highlightEdgeColor));
|
||||
ValueCell.update(overlayValues.uSelectEdgeColor, Color.toVec3Normalized(overlayValues.uSelectEdgeColor.ref.value, selectEdgeColor));
|
||||
ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
|
||||
ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
|
||||
}
|
||||
|
||||
render(viewport: Viewport, target: RenderTarget | undefined) {
|
||||
this.edgesTarget.bind();
|
||||
this.setEdgeState(viewport);
|
||||
this.edge.render();
|
||||
|
||||
if (target) {
|
||||
target.bind();
|
||||
} else {
|
||||
this.webgl.unbindFramebuffer();
|
||||
}
|
||||
this.setOverlayState(viewport);
|
||||
this.overlay.render();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const EdgeSchema = {
|
||||
...QuadSchema,
|
||||
tMaskTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
|
||||
uTexSizeInv: UniformSpec('v2'),
|
||||
dEdgeScale: DefineSpec('number'),
|
||||
};
|
||||
const EdgeShaderCode = ShaderCode('edge', quad_vert, edge_frag);
|
||||
type EdgeRenderable = ComputeRenderable<Values<typeof EdgeSchema>>
|
||||
|
||||
function getEdgeRenderable(ctx: WebGLContext, maskTexture: Texture): EdgeRenderable {
|
||||
const width = maskTexture.getWidth();
|
||||
const height = maskTexture.getHeight();
|
||||
|
||||
const values: Values<typeof EdgeSchema> = {
|
||||
...QuadValues,
|
||||
tMaskTexture: ValueCell.create(maskTexture),
|
||||
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
|
||||
dEdgeScale: ValueCell.create(1),
|
||||
};
|
||||
|
||||
const schema = { ...EdgeSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', EdgeShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const OverlaySchema = {
|
||||
...QuadSchema,
|
||||
tEdgeTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
|
||||
uTexSizeInv: UniformSpec('v2'),
|
||||
uHighlightEdgeColor: UniformSpec('v3'),
|
||||
uSelectEdgeColor: UniformSpec('v3'),
|
||||
uGhostEdgeStrength: UniformSpec('f'),
|
||||
uInnerEdgeFactor: UniformSpec('f'),
|
||||
};
|
||||
const OverlayShaderCode = ShaderCode('overlay', quad_vert, overlay_frag);
|
||||
type OverlayRenderable = ComputeRenderable<Values<typeof OverlaySchema>>
|
||||
|
||||
function getOverlayRenderable(ctx: WebGLContext, edgeTexture: Texture): OverlayRenderable {
|
||||
const width = edgeTexture.getWidth();
|
||||
const height = edgeTexture.getHeight();
|
||||
|
||||
const values: Values<typeof OverlaySchema> = {
|
||||
...QuadValues,
|
||||
tEdgeTexture: ValueCell.create(edgeTexture),
|
||||
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
|
||||
uHighlightEdgeColor: ValueCell.create(Vec3()),
|
||||
uSelectEdgeColor: ValueCell.create(Vec3()),
|
||||
uGhostEdgeStrength: ValueCell.create(0),
|
||||
uInnerEdgeFactor: ValueCell.create(0),
|
||||
};
|
||||
|
||||
const schema = { ...OverlaySchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', OverlayShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
@@ -22,9 +22,9 @@ import { Renderer } from '../../mol-gl/renderer';
|
||||
import { Scene } from '../../mol-gl/scene';
|
||||
import { Helper } from '../helper/helper';
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
|
||||
import { quad_vert } from '../../mol-gl/shader/quad.vert';
|
||||
import { compose_frag } from '../../mol-gl/shader/compose.frag';
|
||||
import { MarkingProps } from './marking';
|
||||
|
||||
const ComposeSchema = {
|
||||
...QuadSchema,
|
||||
@@ -55,7 +55,11 @@ export const MultiSampleParams = {
|
||||
};
|
||||
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
|
||||
|
||||
type Props = { multiSample: MultiSampleProps, postprocessing: PostprocessingProps }
|
||||
type Props = {
|
||||
multiSample: MultiSampleProps
|
||||
postprocessing: PostprocessingProps
|
||||
marking: MarkingProps
|
||||
}
|
||||
|
||||
export class MultiSamplePass {
|
||||
static isEnabled(props: MultiSampleProps) {
|
||||
@@ -144,7 +148,7 @@ export class MultiSamplePass {
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind();
|
||||
@@ -194,7 +198,7 @@ export class MultiSamplePass {
|
||||
const sampleWeight = 1.0 / offsetList.length;
|
||||
|
||||
if (sampleIndex === -1) {
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
|
||||
compose.update();
|
||||
@@ -222,7 +226,7 @@ export class MultiSamplePass {
|
||||
camera.update();
|
||||
|
||||
// render scene
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export const ofBounds = I.ofBounds;
|
||||
export function ofSortedArray(xs: Nums): OrderedSetImpl {
|
||||
if (!xs.length) return Empty;
|
||||
// check if the array is just a range
|
||||
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return I.ofRange(xs[0], xs[xs.length - 1]);
|
||||
if (S.isRange(xs)) return I.ofRange(xs[0], xs[xs.length - 1]);
|
||||
return xs as any;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export function ofRange(min: number, max: number) {
|
||||
return ret;
|
||||
}
|
||||
export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); }
|
||||
export function isRange(xs: Nums) { return xs[xs.length - 1] - xs[0] + 1 === xs.length; }
|
||||
|
||||
export function start(xs: Nums) { return xs[0]; }
|
||||
export function end(xs: Nums) { return xs[xs.length - 1] + 1; }
|
||||
@@ -59,9 +60,11 @@ export function getAt(xs: Nums, i: number) { return xs[i]; }
|
||||
|
||||
export function areEqual(a: Nums, b: Nums) {
|
||||
if (a === b) return true;
|
||||
const aSize = a.length;
|
||||
let aSize = a.length;
|
||||
if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false;
|
||||
for (let i = 0; i < aSize; i++) {
|
||||
if (isRange(a)) return true;
|
||||
aSize--;
|
||||
for (let i = 1; i < aSize; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -340,7 +343,7 @@ export function deduplicate(xs: Nums) {
|
||||
}
|
||||
|
||||
export function indicesOf(a: Nums, b: Nums): Nums {
|
||||
if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1));
|
||||
if (areEqual(a, b)) return ofSortedArray(createRangeArray(0, a.length - 1));
|
||||
|
||||
const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
|
||||
let i = sI, j = sJ;
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace SortedArray {
|
||||
/** create sorted array [min, max) (it does NOT contain the max value) */
|
||||
export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any;
|
||||
export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
|
||||
export const isRange: <T extends number = number>(array: ArrayLike<number>) => boolean = Impl.isRange as any;
|
||||
|
||||
export const has: <T extends number = number>(array: SortedArray<T>, x: T) => boolean = Impl.has as any;
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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/
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -9,20 +9,75 @@ import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
|
||||
|
||||
export type MarkerData = {
|
||||
uMarker: ValueCell<number>,
|
||||
tMarker: ValueCell<TextureImage<Uint8Array>>
|
||||
uMarkerTexDim: ValueCell<Vec2>
|
||||
dMarkerType: ValueCell<string>,
|
||||
markerAverage: ValueCell<number>
|
||||
markerStatus: ValueCell<number>
|
||||
}
|
||||
|
||||
const MarkerCountLut = new Uint8Array(0x0303 + 1);
|
||||
MarkerCountLut[0x0001] = 1;
|
||||
MarkerCountLut[0x0002] = 1;
|
||||
MarkerCountLut[0x0003] = 1;
|
||||
MarkerCountLut[0x0100] = 1;
|
||||
MarkerCountLut[0x0200] = 1;
|
||||
MarkerCountLut[0x0300] = 1;
|
||||
MarkerCountLut[0x0101] = 2;
|
||||
MarkerCountLut[0x0201] = 2;
|
||||
MarkerCountLut[0x0301] = 2;
|
||||
MarkerCountLut[0x0102] = 2;
|
||||
MarkerCountLut[0x0202] = 2;
|
||||
MarkerCountLut[0x0302] = 2;
|
||||
MarkerCountLut[0x0103] = 2;
|
||||
MarkerCountLut[0x0203] = 2;
|
||||
MarkerCountLut[0x0303] = 2;
|
||||
|
||||
/**
|
||||
* Calculates the average number of entries that have any marker flag set.
|
||||
*
|
||||
* For alternative implementations and performance tests see
|
||||
* `src\perf-tests\markers-average.ts`.
|
||||
*/
|
||||
export function getMarkersAverage(array: Uint8Array, count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
const viewEnd = (count - 4) >> 2;
|
||||
const backStart = 4 * viewEnd;
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < viewEnd; ++i) {
|
||||
const v = view[i];
|
||||
sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
|
||||
}
|
||||
for (let i = backStart; i < count; ++i) {
|
||||
sum += array[i] && 1;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
|
||||
const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array);
|
||||
const average = getMarkersAverage(markers.array, count);
|
||||
const status = average === 0 ? 0 : -1;
|
||||
if (markerData) {
|
||||
ValueCell.updateIfChanged(markerData.uMarker, 0);
|
||||
ValueCell.update(markerData.tMarker, markers);
|
||||
ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height));
|
||||
ValueCell.updateIfChanged(markerData.dMarkerType, status === -1 ? 'groupInstance' : 'uniform');
|
||||
ValueCell.updateIfChanged(markerData.markerAverage, average);
|
||||
ValueCell.updateIfChanged(markerData.markerStatus, status);
|
||||
return markerData;
|
||||
} else {
|
||||
return {
|
||||
uMarker: ValueCell.create(0),
|
||||
tMarker: ValueCell.create(markers),
|
||||
uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)),
|
||||
markerAverage: ValueCell.create(average),
|
||||
markerStatus: ValueCell.create(status),
|
||||
dMarkerType: ValueCell.create('uniform'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -30,13 +85,21 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
|
||||
const emptyMarkerTexture = { array: new Uint8Array(1), width: 1, height: 1 };
|
||||
export function createEmptyMarkers(markerData?: MarkerData): MarkerData {
|
||||
if (markerData) {
|
||||
ValueCell.updateIfChanged(markerData.uMarker, 0);
|
||||
ValueCell.update(markerData.tMarker, emptyMarkerTexture);
|
||||
ValueCell.update(markerData.uMarkerTexDim, Vec2.create(1, 1));
|
||||
ValueCell.updateIfChanged(markerData.dMarkerType, 'uniform');
|
||||
ValueCell.updateIfChanged(markerData.markerAverage, 0);
|
||||
ValueCell.updateIfChanged(markerData.markerStatus, 0);
|
||||
return markerData;
|
||||
} else {
|
||||
return {
|
||||
uMarker: ValueCell.create(0),
|
||||
tMarker: ValueCell.create(emptyMarkerTexture),
|
||||
uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)),
|
||||
markerAverage: ValueCell.create(0),
|
||||
markerStatus: ValueCell.create(0),
|
||||
dMarkerType: ValueCell.create('uniform'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -134,17 +134,17 @@ describe('renderer', () => {
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(7);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(8);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(8);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(16);
|
||||
|
||||
scene.remove(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(8);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(16);
|
||||
|
||||
ctx.resources.destroy();
|
||||
expect(ctx.stats.resourceCounts.program).toBe(0);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -106,7 +106,6 @@ export type RenderableSchema = {
|
||||
}
|
||||
export type RenderableValues = { readonly [k: string]: ValueCell<any> }
|
||||
|
||||
|
||||
//
|
||||
|
||||
export const GlobalUniformSchema = {
|
||||
@@ -161,10 +160,13 @@ export const GlobalUniformSchema = {
|
||||
|
||||
uHighlightColor: UniformSpec('v3'),
|
||||
uSelectColor: UniformSpec('v3'),
|
||||
uHighlightStrength: UniformSpec('f'),
|
||||
uSelectStrength: UniformSpec('f'),
|
||||
|
||||
uXrayEdgeFalloff: UniformSpec('f'),
|
||||
|
||||
uRenderWboit: UniformSpec('b'),
|
||||
uMarkingDepthTest: UniformSpec('b'),
|
||||
} as const;
|
||||
export type GlobalUniformSchema = typeof GlobalUniformSchema
|
||||
export type GlobalUniformValues = Values<GlobalUniformSchema>
|
||||
@@ -208,8 +210,12 @@ export type SizeSchema = typeof SizeSchema
|
||||
export type SizeValues = Values<SizeSchema>
|
||||
|
||||
export const MarkerSchema = {
|
||||
uMarker: UniformSpec('f'),
|
||||
uMarkerTexDim: UniformSpec('v2'),
|
||||
tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
|
||||
dMarkerType: DefineSpec('string', ['uniform', 'groupInstance']),
|
||||
markerAverage: ValueSpec('number'),
|
||||
markerStatus: ValueSpec('number'),
|
||||
} as const;
|
||||
export type MarkerSchema = typeof MarkerSchema
|
||||
export type MarkerValues = Values<MarkerSchema>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -48,6 +48,8 @@ interface Renderer {
|
||||
|
||||
renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => void
|
||||
renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
@@ -76,6 +78,8 @@ export const RendererParams = {
|
||||
|
||||
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
|
||||
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
|
||||
highlightStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
|
||||
selectStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
|
||||
|
||||
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
|
||||
|
||||
@@ -242,6 +246,7 @@ namespace Renderer {
|
||||
uFogColor: ValueCell.create(bgColor),
|
||||
|
||||
uRenderWboit: ValueCell.create(false),
|
||||
uMarkingDepthTest: ValueCell.create(false),
|
||||
|
||||
uTransparentBackground: ValueCell.create(false),
|
||||
|
||||
@@ -267,6 +272,8 @@ namespace Renderer {
|
||||
|
||||
uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
|
||||
uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
|
||||
uHighlightStrength: ValueCell.create(p.highlightStrength),
|
||||
uSelectStrength: ValueCell.create(p.selectStrength),
|
||||
|
||||
uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff),
|
||||
};
|
||||
@@ -375,7 +382,7 @@ namespace Renderer {
|
||||
ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
|
||||
};
|
||||
|
||||
const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean) => {
|
||||
const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => {
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture);
|
||||
|
||||
ValueCell.update(globalUniforms.uModel, group.view);
|
||||
@@ -385,6 +392,7 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
|
||||
|
||||
ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit);
|
||||
ValueCell.updateIfChanged(globalUniforms.uMarkingDepthTest, markingDepthTest);
|
||||
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.colorMask(true, true, true, true);
|
||||
@@ -402,7 +410,7 @@ namespace Renderer {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
@@ -417,7 +425,7 @@ namespace Renderer {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
@@ -425,6 +433,40 @@ namespace Renderer {
|
||||
}
|
||||
};
|
||||
|
||||
const renderMarkingDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
state.disable(gl.BLEND);
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
if (r.values.markerAverage.ref.value !== 1) {
|
||||
renderObject(renderables[i], 'markingDepth');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderMarkingMask = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
state.disable(gl.BLEND);
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false, !!depthTexture);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
if (r.values.markerAverage.ref.value > 0) {
|
||||
renderObject(renderables[i], 'markingMask');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
renderBlendedOpaque(group, camera, depthTexture);
|
||||
renderBlendedTransparent(group, camera, depthTexture);
|
||||
@@ -435,7 +477,7 @@ namespace Renderer {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
@@ -449,7 +491,7 @@ namespace Renderer {
|
||||
const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
|
||||
@@ -481,13 +523,13 @@ namespace Renderer {
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
state.enable(gl.BLEND);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) {
|
||||
@@ -500,13 +542,13 @@ namespace Renderer {
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
state.enable(gl.BLEND);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) {
|
||||
@@ -520,13 +562,13 @@ namespace Renderer {
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, false);
|
||||
updateInternal(group, camera, depthTexture, false, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
|
||||
@@ -536,13 +578,13 @@ namespace Renderer {
|
||||
};
|
||||
|
||||
const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
updateInternal(group, camera, depthTexture, true);
|
||||
updateInternal(group, camera, depthTexture, true, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
@@ -577,6 +619,8 @@ namespace Renderer {
|
||||
|
||||
renderPick,
|
||||
renderDepth,
|
||||
renderMarkingDepth,
|
||||
renderMarkingMask,
|
||||
renderBlended,
|
||||
renderBlendedOpaque,
|
||||
renderBlendedTransparent,
|
||||
@@ -618,6 +662,14 @@ namespace Renderer {
|
||||
p.selectColor = props.selectColor;
|
||||
ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
|
||||
}
|
||||
if (props.highlightStrength !== undefined && props.highlightStrength !== p.highlightStrength) {
|
||||
p.highlightStrength = props.highlightStrength;
|
||||
ValueCell.update(globalUniforms.uHighlightStrength, p.highlightStrength);
|
||||
}
|
||||
if (props.selectStrength !== undefined && props.selectStrength !== p.selectStrength) {
|
||||
p.selectStrength = props.selectStrength;
|
||||
ValueCell.update(globalUniforms.uSelectStrength, p.selectStrength);
|
||||
}
|
||||
|
||||
if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) {
|
||||
p.xrayEdgeFalloff = props.xrayEdgeFalloff;
|
||||
|
||||
@@ -65,7 +65,6 @@ import { size_vert_params } from './shader/chunks/size-vert-params.glsl';
|
||||
import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-trilinear.glsl';
|
||||
import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl';
|
||||
import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl';
|
||||
import { wboit_params } from './shader/chunks/wboit-params.glsl';
|
||||
import { wboit_write } from './shader/chunks/wboit-write.glsl';
|
||||
|
||||
const ShaderChunks: { [k: string]: string } = {
|
||||
@@ -99,7 +98,6 @@ const ShaderChunks: { [k: string]: string } = {
|
||||
texture3d_from_1d_trilinear,
|
||||
texture3d_from_2d_linear,
|
||||
texture3d_from_2d_nearest,
|
||||
wboit_params,
|
||||
wboit_write
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export const apply_marker_color = `
|
||||
float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
if (marker > 0.1) {
|
||||
if (intMod(marker, 2.0) > 0.1) {
|
||||
gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3);
|
||||
gl_FragColor.a = max(0.02, gl_FragColor.a); // for direct-volume rendering
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uHighlightColor, uHighlightStrength);
|
||||
gl_FragColor.a = max(gl_FragColor.a, uHighlightStrength * 0.002); // for direct-volume rendering
|
||||
} else {
|
||||
gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3);
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uSelectColor, uSelectStrength);
|
||||
gl_FragColor.a = max(gl_FragColor.a, uSelectStrength * 0.002); // for direct-volume rendering
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,3 +1,5 @@
|
||||
export const assign_marker_varying = `
|
||||
vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
#if defined(dMarkerType_groupInstance)
|
||||
vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
#endif
|
||||
`;
|
||||
@@ -1,4 +1,13 @@
|
||||
export const assign_material_color = `
|
||||
#if defined(dRenderVariant_color) || defined(dRenderVariant_marking)
|
||||
#if defined(dMarkerType_uniform)
|
||||
float marker = uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
float marker = vMarker;
|
||||
#endif
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dUsePalette)
|
||||
vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha);
|
||||
@@ -20,6 +29,27 @@ export const assign_material_color = `
|
||||
#else
|
||||
vec4 material = packDepthToRGBA(gl_FragCoord.z);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_markingDepth)
|
||||
if (marker > 0.0)
|
||||
discard;
|
||||
#ifdef enabledFragDepth
|
||||
vec4 material = packDepthToRGBA(gl_FragDepthEXT);
|
||||
#else
|
||||
vec4 material = packDepthToRGBA(gl_FragCoord.z);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_markingMask)
|
||||
if (marker == 0.0)
|
||||
discard;
|
||||
float depthTest = 1.0;
|
||||
if (uMarkingDepthTest) {
|
||||
depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
|
||||
}
|
||||
bool isHighlight = intMod(marker, 2.0) > 0.1;
|
||||
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
|
||||
if (fogFactor == 1.0)
|
||||
discard;
|
||||
vec4 material = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0 - fogFactor);
|
||||
#endif
|
||||
|
||||
// apply screendoor transparency
|
||||
|
||||
@@ -21,10 +21,17 @@ uniform int uGroupCount;
|
||||
|
||||
uniform vec3 uHighlightColor;
|
||||
uniform vec3 uSelectColor;
|
||||
#if __VERSION__ == 100
|
||||
varying float vMarker;
|
||||
#else
|
||||
flat in float vMarker;
|
||||
uniform float uHighlightStrength;
|
||||
uniform float uSelectStrength;
|
||||
|
||||
#if defined(dMarkerType_uniform)
|
||||
uniform float uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
#if __VERSION__ == 100
|
||||
varying float vMarker;
|
||||
#else
|
||||
flat in float vMarker;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
varying vec3 vModelPosition;
|
||||
@@ -52,4 +59,20 @@ bool interior;
|
||||
uniform float uXrayEdgeFalloff;
|
||||
|
||||
uniform mat4 uProjection;
|
||||
|
||||
uniform bool uRenderWboit;
|
||||
uniform bool uMarkingDepthTest;
|
||||
|
||||
uniform sampler2D tDepth;
|
||||
uniform vec2 uDrawingBufferSize;
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
// always packed due to merged depth from primitives and volumes
|
||||
return unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
}
|
||||
|
||||
float calcDepth(const in vec3 pos) {
|
||||
vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw;
|
||||
return 0.5 + 0.5 * clipZW.x / clipZW.y;
|
||||
}
|
||||
`;
|
||||
@@ -26,12 +26,16 @@ uniform vec4 uInvariantBoundingSphere;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uniform vec2 uMarkerTexDim;
|
||||
uniform sampler2D tMarker;
|
||||
#if __VERSION__ == 100
|
||||
varying float vMarker;
|
||||
#else
|
||||
flat out float vMarker;
|
||||
#if defined(dMarkerType_uniform)
|
||||
uniform float uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
uniform vec2 uMarkerTexDim;
|
||||
uniform sampler2D tMarker;
|
||||
#if __VERSION__ == 100
|
||||
varying float vMarker;
|
||||
#else
|
||||
flat out float vMarker;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
varying vec3 vModelPosition;
|
||||
|
||||
@@ -9,6 +9,10 @@ export const common = `
|
||||
#define dRenderVariant_pick
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_markingDepth) || defined(dRenderVariant_markingMask)
|
||||
#define dRenderVariant_marking
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance) || defined(dColorType_vertex) || defined(dColorType_vertexInstance)
|
||||
#define dColorType_texture
|
||||
#endif
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export const wboit_params = `
|
||||
#if defined(dRenderVariant_colorWboit)
|
||||
#if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface)
|
||||
uniform sampler2D tDepth;
|
||||
uniform vec2 uDrawingBufferSize;
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
// always packed due to merged depth from primitives and volumes
|
||||
return unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uniform bool uRenderWboit;
|
||||
|
||||
float calcDepth(const in vec3 pos) {
|
||||
vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw;
|
||||
return 0.5 + 0.5 * clipZW.x / clipZW.y;
|
||||
}
|
||||
`;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -24,7 +24,6 @@ uniform vec3 uCameraPosition;
|
||||
#include color_frag_params
|
||||
#include light_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
// adapted from https://www.shadertoy.com/view/4lcSRn
|
||||
// The MIT License, Copyright 2016 Inigo Quilez
|
||||
@@ -121,6 +120,8 @@ void main() {
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
|
||||
@@ -53,8 +53,15 @@ uniform int uGroupCount;
|
||||
|
||||
uniform vec3 uHighlightColor;
|
||||
uniform vec3 uSelectColor;
|
||||
uniform vec2 uMarkerTexDim;
|
||||
uniform sampler2D tMarker;
|
||||
uniform float uHighlightStrength;
|
||||
uniform float uSelectStrength;
|
||||
|
||||
#if defined(dMarkerType_uniform)
|
||||
uniform float uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
uniform vec2 uMarkerTexDim;
|
||||
uniform sampler2D tMarker;
|
||||
#endif
|
||||
|
||||
uniform float uFogNear;
|
||||
uniform float uFogFar;
|
||||
@@ -69,6 +76,8 @@ uniform bool uInteriorColorFlag;
|
||||
uniform vec3 uInteriorColor;
|
||||
bool interior;
|
||||
|
||||
uniform bool uRenderWboit;
|
||||
|
||||
uniform float uNear;
|
||||
uniform float uFar;
|
||||
uniform float uIsOrtho;
|
||||
@@ -122,7 +131,10 @@ uniform mat4 uCartnToUnit;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include wboit_params
|
||||
float calcDepth(const in vec3 pos) {
|
||||
vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw;
|
||||
return 0.5 + 0.5 * clipZW.x / clipZW.y;
|
||||
}
|
||||
|
||||
vec4 transferFunction(float value) {
|
||||
return texture2D(tTransferTex, vec2(value, 0.0));
|
||||
@@ -322,7 +334,12 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
#include apply_light_color
|
||||
#endif
|
||||
|
||||
float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
#if defined(dMarkerType_uniform)
|
||||
float marker = uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -385,14 +402,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
|
||||
gl_FragColor.a = material.a * uAlpha * uTransferScale;
|
||||
|
||||
#ifdef dPackedGroup
|
||||
float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
|
||||
#else
|
||||
vec3 g = floor(unitPos * uGridDim + 0.5);
|
||||
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
|
||||
#if defined(dMarkerType_uniform)
|
||||
float marker = uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
#ifdef dPackedGroup
|
||||
float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
|
||||
#else
|
||||
vec3 g = floor(unitPos * uGridDim + 0.5);
|
||||
float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
|
||||
#endif
|
||||
float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
|
||||
float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
#include apply_marker_color
|
||||
|
||||
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
|
||||
@@ -432,6 +453,11 @@ void main() {
|
||||
if (gl_FrontFacing)
|
||||
discard;
|
||||
|
||||
#ifdef dRenderVariant_marking
|
||||
// not supported
|
||||
discard;
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
|
||||
#if defined(dRenderMode_volume)
|
||||
// always ignore pick & depth for volume
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,7 +11,6 @@ precision highp int;
|
||||
#include read_from_texture
|
||||
#include common_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
uniform vec2 uImageTexDim;
|
||||
uniform sampler2D tImageTex;
|
||||
@@ -105,7 +104,6 @@ void main() {
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (imageData.a < 0.3)
|
||||
discard;
|
||||
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dRenderVariant_pickInstance)
|
||||
@@ -116,17 +114,42 @@ void main() {
|
||||
#elif defined(dRenderVariant_depth)
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
|
||||
gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
|
||||
#elif defined(dRenderVariant_marking)
|
||||
#if defined(dMarkerType_uniform)
|
||||
float marker = uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
|
||||
float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
#if defined(dRenderVariant_markingDepth)
|
||||
if (marker > 0.0 || imageData.a < 0.05)
|
||||
discard;
|
||||
gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
|
||||
#elif defined(dRenderVariant_markingMask)
|
||||
if (marker == 0.0 || imageData.a < 0.05)
|
||||
discard;
|
||||
float depthTest = 1.0;
|
||||
if (uMarkingDepthTest) {
|
||||
depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
|
||||
}
|
||||
bool isHighlight = intMod(marker, 2.0) > 0.1;
|
||||
gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_color)
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
|
||||
gl_FragColor = imageData;
|
||||
gl_FragColor.a *= uAlpha;
|
||||
|
||||
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
|
||||
float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
#if defined(dMarkerType_uniform)
|
||||
float marker = uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
|
||||
float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -12,7 +12,6 @@ precision highp int;
|
||||
#include common_frag_params
|
||||
#include color_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
void main(){
|
||||
#include clip_pixel
|
||||
@@ -26,6 +25,8 @@ void main(){
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
gl_FragColor = material;
|
||||
|
||||
|
||||
29
src/mol-gl/shader/marking/edge.frag.ts
Normal file
29
src/mol-gl/shader/marking/edge.frag.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const edge_frag = `
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tMaskTexture;
|
||||
uniform vec2 uTexSizeInv;
|
||||
|
||||
void main() {
|
||||
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
|
||||
vec4 offset = vec4(float(dEdgeScale), 0.0, 0.0, float(dEdgeScale)) * vec4(uTexSizeInv, uTexSizeInv);
|
||||
vec4 c0 = texture2D(tMaskTexture, coords);
|
||||
vec4 c1 = texture2D(tMaskTexture, coords + offset.xy);
|
||||
vec4 c2 = texture2D(tMaskTexture, coords - offset.xy);
|
||||
vec4 c3 = texture2D(tMaskTexture, coords + offset.yw);
|
||||
vec4 c4 = texture2D(tMaskTexture, coords - offset.yw);
|
||||
float diff1 = (c1.r - c2.r) * 0.5;
|
||||
float diff2 = (c3.r - c4.r) * 0.5;
|
||||
float d = length(vec2(diff1, diff2));
|
||||
if (d <= 0.0)
|
||||
discard;
|
||||
float a1 = min(c1.g, c2.g);
|
||||
float a2 = min(c3.g, c4.g);
|
||||
float visibility = min(a1, a2) > 0.001 ? 1.0 : 0.0;
|
||||
float mask = c0.r;
|
||||
float marker = min(c1.b, min(c2.b, min(c3.b, c4.b)));
|
||||
float fogAlpha = min(c1.a, min(c2.a, min(c3.a, c4.a)));
|
||||
gl_FragColor = vec4(visibility, mask, marker, fogAlpha);
|
||||
}
|
||||
`;
|
||||
23
src/mol-gl/shader/marking/overlay.frag.ts
Normal file
23
src/mol-gl/shader/marking/overlay.frag.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const overlay_frag = `
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform vec2 uTexSizeInv;
|
||||
uniform sampler2D tEdgeTexture;
|
||||
uniform vec3 uHighlightEdgeColor;
|
||||
uniform vec3 uSelectEdgeColor;
|
||||
uniform float uGhostEdgeStrength;
|
||||
uniform float uInnerEdgeFactor;
|
||||
|
||||
void main() {
|
||||
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
|
||||
vec4 edgeValue = texture2D(tEdgeTexture, coords);
|
||||
if (edgeValue.a > 0.0) {
|
||||
vec3 edgeColor = edgeValue.b == 1.0 ? uHighlightEdgeColor : uSelectEdgeColor;
|
||||
gl_FragColor.rgb = edgeValue.g > 0.0 ? edgeColor : edgeColor * uInnerEdgeFactor;
|
||||
gl_FragColor.a = (edgeValue.r == 1.0 ? uGhostEdgeStrength : 1.0) * edgeValue.a;
|
||||
} else {
|
||||
gl_FragColor = vec4(0.0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -14,7 +14,6 @@ precision highp int;
|
||||
#include light_frag_params
|
||||
#include normal_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
void main() {
|
||||
#include clip_pixel
|
||||
@@ -43,6 +42,8 @@ void main() {
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -12,7 +12,6 @@ precision highp int;
|
||||
#include common_frag_params
|
||||
#include color_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
#ifdef dPointFilledCircle
|
||||
uniform float uPointEdgeBleach;
|
||||
@@ -33,6 +32,8 @@ void main(){
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
gl_FragColor = material;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -13,7 +13,6 @@ precision highp int;
|
||||
#include color_frag_params
|
||||
#include light_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
varying float vRadius;
|
||||
varying float vRadiusSq;
|
||||
@@ -86,6 +85,8 @@ void main(void){
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -12,7 +12,6 @@ precision highp int;
|
||||
#include common_frag_params
|
||||
#include color_frag_params
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
uniform sampler2D tFont;
|
||||
|
||||
@@ -66,6 +65,8 @@ void main(){
|
||||
#include check_picking_alpha
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface RenderItem<T extends string> {
|
||||
|
||||
//
|
||||
|
||||
const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '' };
|
||||
const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '', 'markingDepth': '', 'markingMask': '' };
|
||||
export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant
|
||||
const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/mol-model-formats/structure/sdf.ts
Normal file
29
src/mol-model-formats/structure/sdf.ts
Normal 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));
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
31
src/mol-model-props/computed/helix-orientation.ts
Normal file
31
src/mol-model-props/computed/helix-orientation.ts
Normal 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) };
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Loci {
|
||||
return !!loci && loci.kind === 'every-loci';
|
||||
}
|
||||
|
||||
export function isEmpty(loci: Loci): loci is EmptyLoci {
|
||||
export function isEmpty(loci: Loci) {
|
||||
if (isEveryLoci(loci)) return false;
|
||||
if (isEmptyLoci(loci)) return true;
|
||||
if (isDataLoci(loci)) return isDataLociEmpty(loci);
|
||||
@@ -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)
|
||||
|
||||
@@ -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 } = {
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
@@ -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];
|
||||
@@ -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 {
|
||||
@@ -65,7 +66,10 @@ export namespace Loci {
|
||||
}
|
||||
|
||||
export function isEmpty(loci: Loci) {
|
||||
return size(loci) === 0;
|
||||
for (const u of loci.elements) {
|
||||
if(OrderedSet.size(u.indices) > 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isWholeStructure(loci: Loci) {
|
||||
@@ -74,7 +78,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;
|
||||
}
|
||||
|
||||
@@ -131,7 +143,7 @@ export namespace Loci {
|
||||
return Structure.create(units, { parent: loci.structure.parent });
|
||||
}
|
||||
|
||||
// TODO: there should be a version that property supports partitioned units
|
||||
// TODO: there should be a version that properly supports partitioned units
|
||||
export function remap(loci: Loci, structure: Structure): Loci {
|
||||
if (structure === loci.structure) return loci;
|
||||
|
||||
@@ -241,6 +253,14 @@ export namespace Loci {
|
||||
return isSubset;
|
||||
}
|
||||
|
||||
function makeIndexSet(newIndices: ArrayLike<UnitIndex>): OrderedSet<UnitIndex> {
|
||||
if (newIndices.length > 3 && SortedArray.isRange(newIndices)) {
|
||||
return Interval.ofRange(newIndices[0], newIndices[newIndices.length - 1]);
|
||||
} else {
|
||||
return SortedArray.ofSortedArray(newIndices);
|
||||
}
|
||||
}
|
||||
|
||||
export function extendToWholeResidues(loci: Loci, restrictToConformation?: boolean): Loci {
|
||||
const elements: Loci['elements'][0][] = [];
|
||||
const residueAltIds = new Set<string>();
|
||||
@@ -285,7 +305,7 @@ export namespace Loci {
|
||||
}
|
||||
}
|
||||
|
||||
elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) };
|
||||
elements[elements.length] = { unit: lociElement.unit, indices: makeIndexSet(newIndices) };
|
||||
} else {
|
||||
// coarse elements are already by-residue
|
||||
elements[elements.length] = lociElement;
|
||||
@@ -307,14 +327,6 @@ export namespace Loci {
|
||||
return element.unit.elements.length === OrderedSet.size(element.indices);
|
||||
}
|
||||
|
||||
function makeIndexSet(newIndices: number[]): OrderedSet<UnitIndex> {
|
||||
if (newIndices.length > 12 && newIndices[newIndices.length - 1] - newIndices[0] === newIndices.length - 1) {
|
||||
return Interval.ofRange(newIndices[0], newIndices[newIndices.length - 1]);
|
||||
} else {
|
||||
return SortedArray.ofSortedArray(newIndices);
|
||||
}
|
||||
}
|
||||
|
||||
function collectChains(unit: Unit, chainIndices: Set<ChainIndex>, elements: Loci['elements'][0][]) {
|
||||
const { index } = getChainSegments(unit);
|
||||
const xs = unit.elements;
|
||||
@@ -458,7 +470,10 @@ export namespace Loci {
|
||||
}
|
||||
|
||||
function getUnitIndices(elements: SortedArray<ElementIndex>, indices: SortedArray<ElementIndex>) {
|
||||
return SortedArray.indicesOf<ElementIndex, UnitIndex>(elements, indices);
|
||||
if (SortedArray.isRange(elements) && SortedArray.areEqual(elements, indices)) {
|
||||
return Interval.ofLength(elements.length);
|
||||
}
|
||||
return makeIndexSet(SortedArray.indicesOf<ElementIndex, UnitIndex>(elements, indices));
|
||||
}
|
||||
|
||||
export function extendToAllInstances(loci: Loci): Loci {
|
||||
@@ -488,6 +503,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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
|
||||
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Clipping } from '../../mol-theme/clipping';
|
||||
|
||||
type ClippingEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, clipping?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.ClippingStructureRepresentation3DFromBundle>>) => Promise<void>
|
||||
@@ -25,7 +25,7 @@ export async function setStructureClipping(plugin: PluginContext, components: St
|
||||
// always use the root structure to get the loci so the clipping
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
if (Loci.isEmpty(loci)) return;
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
|
||||
|
||||
const layer = {
|
||||
bundle: StructureElement.Bundle.fromLoci(loci),
|
||||
|
||||
@@ -13,7 +13,7 @@ import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '.
|
||||
import { Overpaint } from '../../mol-theme/overpaint';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
|
||||
|
||||
type OverpaintEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, overpaint?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle>>) => Promise<void>
|
||||
const OverpaintManagerTag = 'overpaint-controls';
|
||||
@@ -26,7 +26,7 @@ export async function setStructureOverpaint(plugin: PluginContext, components: S
|
||||
// always use the root structure to get the loci so the overpaint
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
if (Loci.isEmpty(loci)) return;
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
|
||||
|
||||
const layer = {
|
||||
bundle: StructureElement.Bundle.fromLoci(loci),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
|
||||
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Transparency } from '../../mol-theme/transparency';
|
||||
|
||||
type TransparencyEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, transparency?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.TransparencyStructureRepresentation3DFromBundle>>) => Promise<void>
|
||||
@@ -25,7 +25,7 @@ export async function setStructureTransparency(plugin: PluginContext, components
|
||||
// always use the root structure to get the loci so the transparency
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
if (Loci.isEmpty(loci)) return;
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
|
||||
|
||||
const layer = {
|
||||
bundle: StructureElement.Bundle.fromLoci(loci),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -74,7 +74,13 @@ namespace InteractivityManager {
|
||||
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
|
||||
export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction) => void
|
||||
/**
|
||||
* The `noRender` argument indicates that the action should only update the internal
|
||||
* data structure but not render anything user visible. For example 1) no drawing of
|
||||
* the canvas3d scene or 2) no ui update of loci labels. This is useful because some
|
||||
* actions require clearing any markings before they can be applied.
|
||||
*/
|
||||
export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction, /* test */ noRender?: boolean) => void
|
||||
|
||||
export abstract class LociMarkManager {
|
||||
protected providers: LociMarkProvider[] = [];
|
||||
@@ -101,9 +107,9 @@ namespace InteractivityManager {
|
||||
return { loci: Loci.normalize(loci, granularity), repr };
|
||||
}
|
||||
|
||||
protected mark(current: Representation.Loci, action: MarkerAction) {
|
||||
protected mark(current: Representation.Loci, action: MarkerAction, noRender = false) {
|
||||
if (!Loci.isEmpty(current.loci)) {
|
||||
for (let p of this.providers) p(current, action);
|
||||
for (let p of this.providers) p(current, action, noRender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +136,9 @@ namespace InteractivityManager {
|
||||
this.prev.push(loci);
|
||||
}
|
||||
|
||||
clearHighlights = () => {
|
||||
clearHighlights = (noRender = false) => {
|
||||
for (const p of this.prev) {
|
||||
this.mark(p, MarkerAction.RemoveHighlight);
|
||||
this.mark(p, MarkerAction.RemoveHighlight, noRender);
|
||||
}
|
||||
this.prev.length = 0;
|
||||
}
|
||||
@@ -147,21 +153,29 @@ namespace InteractivityManager {
|
||||
highlightOnly(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
if (!this.isHighlighted(normalized)) {
|
||||
this.clearHighlights();
|
||||
this.addHighlight(normalized);
|
||||
if (Loci.isEmpty(normalized.loci)) {
|
||||
this.clearHighlights();
|
||||
} else {
|
||||
this.clearHighlights(true);
|
||||
this.addHighlight(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlightOnlyExtend(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
const loci = {
|
||||
const extended = {
|
||||
loci: this.sel.tryGetRange(normalized.loci) || normalized.loci,
|
||||
repr: normalized.repr
|
||||
};
|
||||
if (!this.isHighlighted(loci)) {
|
||||
this.clearHighlights();
|
||||
this.addHighlight(loci);
|
||||
if (!this.isHighlighted(extended)) {
|
||||
if (Loci.isEmpty(extended.loci)) {
|
||||
this.clearHighlights();
|
||||
} else {
|
||||
this.clearHighlights(true);
|
||||
this.addHighlight(extended);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,7 +255,7 @@ namespace InteractivityManager {
|
||||
// do a full deselect/select for the current structure so visuals that are
|
||||
// marked with granularity unequal to 'element' and join/intersect operations
|
||||
// are handled properly
|
||||
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect);
|
||||
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect, true);
|
||||
super.mark({ loci: this.sel.getLoci(loci.structure) }, MarkerAction.Select);
|
||||
} else {
|
||||
super.mark(current, action);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -92,9 +92,9 @@ export class LociLabelManager {
|
||||
}
|
||||
|
||||
constructor(public ctx: PluginContext) {
|
||||
ctx.managers.interactivity.lociHighlights.addProvider((loci, action) => {
|
||||
ctx.managers.interactivity.lociHighlights.addProvider((loci, action, noRender) => {
|
||||
this.mark(loci, action);
|
||||
this.showLabels();
|
||||
if (!noRender) this.showLabels();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user