mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d3ac92989 | ||
|
|
d6eb334d12 | ||
|
|
77139afe7f | ||
|
|
54476ad85e | ||
|
|
6dd876232d | ||
|
|
ae2314d76c | ||
|
|
6667509745 | ||
|
|
5c871a5aae | ||
|
|
2482ef92af | ||
|
|
db59303a84 | ||
|
|
fe700953ff | ||
|
|
047946e41c | ||
|
|
2fe43eda2b | ||
|
|
45fc0c61af | ||
|
|
7e7993f5ba | ||
|
|
3e9de449c8 | ||
|
|
0132c7ef5e | ||
|
|
aa2222c086 | ||
|
|
f892917e1c | ||
|
|
f011025f16 | ||
|
|
9e44cd83fa | ||
|
|
e0e45b64ac | ||
|
|
f10b152252 | ||
|
|
b983df7eb5 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -7,6 +7,16 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [v2.2.3] - 2021-08-25
|
||||
|
||||
- Add ``invertCantorPairing`` helper function
|
||||
- Add ``Mesh`` processing helper ``.smoothEdges``
|
||||
- Smooth border of molecular-surface with ``includeParent`` enabled
|
||||
- Hide ``includeParent`` option from gaussian-surface visuals (not particularly useful)
|
||||
- Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models)
|
||||
- Fix new ``TransformData`` issues (camera/bounding helper not showing up)
|
||||
- Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``)
|
||||
|
||||
## [v2.2.2] - 2021-08-11
|
||||
|
||||
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"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' }),
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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/
|
||||
*/
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -60,11 +60,8 @@ 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 {
|
||||
transformData = {
|
||||
aTransform: ValueCell.create(new Float32Array(instanceCount * 16)),
|
||||
matrix: ValueCell.create(Mat4.identity()),
|
||||
transform: ValueCell.create(new Float32Array(transformArray)),
|
||||
@@ -75,6 +72,9 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
|
||||
hasReflection: ValueCell.create(hasReflection),
|
||||
};
|
||||
}
|
||||
|
||||
updateTransformData(transformData);
|
||||
return transformData;
|
||||
}
|
||||
|
||||
const identityTransform = new Float32Array(16);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -22,9 +22,10 @@ import { NumberArray } from '../../../../mol-util/type-helpers';
|
||||
import { StructureProperties } from '../properties';
|
||||
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
|
||||
import { Boundary } from '../../../../mol-math/geometry/boundary';
|
||||
import { IntTuple } from '../../../../mol-data/int/tuple';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const osSize = OrderedSet.size;
|
||||
const itDiff = IntTuple.diff;
|
||||
|
||||
/** Represents multiple structure element index locations */
|
||||
export interface Loci {
|
||||
@@ -74,7 +75,15 @@ export namespace Loci {
|
||||
|
||||
export function size(loci: Loci) {
|
||||
let s = 0;
|
||||
for (const u of loci.elements) s += osSize(u.indices);
|
||||
// inlined for max performance, crucial for marking large cellpack models
|
||||
// `for (const u of loci.elements) s += OrderedSet.size(u.indices);`
|
||||
for (const { indices } of loci.elements) {
|
||||
if (typeof indices === 'number') {
|
||||
s += itDiff(indices as IntTuple);
|
||||
} else {
|
||||
s += (indices as SortedArray).length;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
|
||||
import { EmptyLoci, Loci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
|
||||
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
|
||||
import { Overpaint } from '../../mol-theme/overpaint';
|
||||
import { StructureParams } from './params';
|
||||
@@ -77,6 +77,10 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
|
||||
// Remap `loci` from equivalent structure to the current `_structure`
|
||||
loci = Loci.remap(loci, _structure);
|
||||
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
|
||||
// Change to `EveryLoci` to allow for downstream optimizations
|
||||
loci = EveryLoci;
|
||||
}
|
||||
} else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
|
||||
import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci, EveryLoci } from '../../mol-model/loci';
|
||||
import { MarkerAction, MarkerActions, applyMarkerAction } from '../../mol-util/marker-action';
|
||||
import { Overpaint } from '../../mol-theme/overpaint';
|
||||
import { Transparency } from '../../mol-theme/transparency';
|
||||
@@ -196,6 +196,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
|
||||
// Remap `loci` from equivalent structure to the current `_structure`
|
||||
loci = Loci.remap(loci, _structure);
|
||||
if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
|
||||
// Change to `EveryLoci` to allow for downstream optimizations
|
||||
loci = EveryLoci;
|
||||
}
|
||||
} else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export const GaussianDensityVolumeParams = {
|
||||
...ComplexDirectVolumeParams,
|
||||
...GaussianDensityParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
export type GaussianDensityVolumeParams = typeof GaussianDensityVolumeParams
|
||||
|
||||
@@ -99,6 +100,7 @@ export const UnitsGaussianDensityVolumeParams = {
|
||||
...UnitsDirectVolumeParams,
|
||||
...GaussianDensityParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
export type UnitsGaussianDensityVolumeParams = typeof UnitsGaussianDensityVolumeParams
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ const SharedParams = {
|
||||
...ColorSmoothingParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
type SharedParams = typeof SharedParams
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export const GaussianWireframeParams = {
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
includeParent: PD.Boolean(false, { isHidden: true }),
|
||||
};
|
||||
export type GaussianWireframeParams = typeof GaussianWireframeParams
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molecular-surface';
|
||||
import { computeUnitMolecularSurface } from './util/molecular-surface';
|
||||
import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/algorithm';
|
||||
import { ElementIterator, getElementLoci, eachElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
@@ -29,6 +29,7 @@ export const MolecularSurfaceMeshParams = {
|
||||
...ColorSmoothingParams,
|
||||
};
|
||||
export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams
|
||||
export type MolecularSurfaceMeshProps = PD.Values<MolecularSurfaceMeshParams>
|
||||
|
||||
type MolecularSurfaceMeta = {
|
||||
resolution?: number
|
||||
@@ -37,7 +38,7 @@ type MolecularSurfaceMeta = {
|
||||
|
||||
//
|
||||
|
||||
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, mesh?: Mesh): Promise<Mesh> {
|
||||
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> {
|
||||
const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
@@ -47,6 +48,11 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
|
||||
};
|
||||
const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
|
||||
|
||||
if (props.includeParent) {
|
||||
const iterations = Math.ceil(2 / props.resolution);
|
||||
Mesh.smoothEdges(surface, { iterations, maxNewEdgeLength: Math.sqrt(2) });
|
||||
}
|
||||
|
||||
Mesh.transform(surface, transform);
|
||||
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
|
||||
|
||||
@@ -71,6 +77,7 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
|
||||
if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
|
||||
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
|
||||
if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
|
||||
|
||||
if (newProps.smoothColors.name !== currentProps.smoothColors.name) {
|
||||
state.updateColor = true;
|
||||
} else if (newProps.smoothColors.name === 'on' && currentProps.smoothColors.name === 'on') {
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
export { PixelData };
|
||||
|
||||
interface PixelData {
|
||||
readonly array: Uint8Array
|
||||
readonly array: Uint8Array | Float32Array
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
}
|
||||
|
||||
namespace PixelData {
|
||||
export function create(array: Uint8Array, width: number, height: number): PixelData {
|
||||
export function create(array: Uint8Array | Float32Array, width: number, height: number): PixelData {
|
||||
return { array, width, height };
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@ namespace PixelData {
|
||||
/** to undo pre-multiplied alpha */
|
||||
export function divideByAlpha(pixelData: PixelData): PixelData {
|
||||
const { array } = pixelData;
|
||||
const factor = (array instanceof Uint8Array) ? 255 : 1;
|
||||
for (let i = 0, il = array.length; i < il; i += 4) {
|
||||
const a = array[i + 3] / 255;
|
||||
const a = array[i + 3] / factor;
|
||||
array[i] /= a;
|
||||
array[i + 1] /= a;
|
||||
array[i + 2] /= a;
|
||||
|
||||
@@ -33,7 +33,7 @@ function test() {
|
||||
fontAtlas.get(String.fromCharCode(0x212B));
|
||||
console.timeEnd('Angstrom Sign');
|
||||
|
||||
printTextureImage(fontAtlas.texture, 0.5);
|
||||
printTextureImage(fontAtlas.texture, { scale: 0.5 });
|
||||
console.log(`${Object.keys(fontAtlas.mapped).length} chars prepared`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user