mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40933a8539 | ||
|
|
989800783b | ||
|
|
d83b0d2c4d | ||
|
|
5e5d5a63dc | ||
|
|
b1755604e2 | ||
|
|
e58da9b574 | ||
|
|
07f351888f | ||
|
|
4588fdd5d5 | ||
|
|
c3b32baf6a | ||
|
|
b8d60cea9b | ||
|
|
25b8956712 | ||
|
|
7015309db6 | ||
|
|
aad861db37 | ||
|
|
ae7811705d | ||
|
|
7e26dac50b | ||
|
|
75f43d038c | ||
|
|
b9ba940510 | ||
|
|
35603baaaa | ||
|
|
19dc32c491 | ||
|
|
95997e6a61 | ||
|
|
03e19a2ad7 | ||
|
|
9f440f68e0 | ||
|
|
40028b27ba | ||
|
|
4676ad8738 | ||
|
|
e1c7833826 | ||
|
|
c38ab2c638 | ||
|
|
459c5aa5a7 | ||
|
|
b8bf07d393 | ||
|
|
ea87ac2094 | ||
|
|
e1b830a59d | ||
|
|
41e1ac76c0 | ||
|
|
98b118fd1e | ||
|
|
5f691913e4 | ||
|
|
26e2516097 | ||
|
|
3d2e4115ed | ||
|
|
dbce1ccb3d | ||
|
|
03aa2be978 | ||
|
|
8dfc52e1ab | ||
|
|
6058179f10 | ||
|
|
ea9e25b03c | ||
|
|
d60c3ddce3 | ||
|
|
724e79bddf | ||
|
|
2de61215c4 | ||
|
|
e783d9a9f1 | ||
|
|
e9e971d4f3 | ||
|
|
96dea14cb1 | ||
|
|
04fc157340 | ||
|
|
cfc24fa99e | ||
|
|
19c1088209 | ||
|
|
ee6c2e0841 | ||
|
|
20ee659b00 | ||
|
|
b6514a4a50 | ||
|
|
07b8bdb951 | ||
|
|
249e5a3e0b | ||
|
|
6d2578d3d0 | ||
|
|
146022dc12 | ||
|
|
5664e1d8be | ||
|
|
4881a41256 | ||
|
|
70e07be64d | ||
|
|
8147b3aa34 | ||
|
|
b21552ff36 | ||
|
|
c683cbe962 | ||
|
|
bd270e4428 | ||
|
|
23d942d8a5 | ||
|
|
cbcd6b99d2 | ||
|
|
ee5c098a9f | ||
|
|
cd872b47e6 | ||
|
|
2683c5b318 | ||
|
|
c71f60a164 | ||
|
|
881cbc1947 | ||
|
|
f3e7febbd1 | ||
|
|
e68ad13031 | ||
|
|
7fbbe1e63a | ||
|
|
a5ca72af3c | ||
|
|
1ce6641eb3 | ||
|
|
5dc413ab8c | ||
|
|
50b615e86c | ||
|
|
5b4c6743e7 |
@@ -24,4 +24,5 @@
|
||||
* Close backbone atoms but not linked (e.g. 4HIV)
|
||||
* Non-standard residues
|
||||
* Protein (1BRR, 5Z6Y)
|
||||
* DNA (5D3G)
|
||||
* DNA (5D3G)
|
||||
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
|
||||
|
||||
25493
package-lock.json
generated
25493
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
76
package.json
76
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.12",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -86,64 +86,64 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.17.7",
|
||||
"@graphql-codegen/cli": "^1.17.8",
|
||||
"@graphql-codegen/time": "^1.17.10",
|
||||
"@graphql-codegen/typescript": "^1.17.9",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.17.8",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.17.7",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.8",
|
||||
"@types/cors": "^2.8.7",
|
||||
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"@graphql-codegen/add": "^2.0.2",
|
||||
"@graphql-codegen/cli": "^1.19.4",
|
||||
"@graphql-codegen/time": "^2.0.2",
|
||||
"@graphql-codegen/typescript": "^1.19.0",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.12",
|
||||
"@types/cors": "^2.8.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.9.1",
|
||||
"@typescript-eslint/parser": "^4.9.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^5.3.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^7.8.1",
|
||||
"cpx2": "^3.0.0",
|
||||
"css-loader": "^5.0.1",
|
||||
"eslint": "^7.15.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"graphql": "^15.3.0",
|
||||
"graphql": "^15.4.0",
|
||||
"http-server": "^0.12.3",
|
||||
"jest": "^26.4.2",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"raw-loader": "^4.0.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^2.20.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"ts-jest": "^26.3.0",
|
||||
"typescript": "^4.0.2",
|
||||
"jest": "^26.6.3",
|
||||
"mini-css-extract-plugin": "^1.3.2",
|
||||
"node-sass": "^5.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^10.1.0",
|
||||
"simple-git": "^2.25.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^4.1.2",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.33",
|
||||
"@types/benchmark": "^2.1.0",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/jest": "^25.2.3",
|
||||
"@types/node": "^14.10.1",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/jest": "^26.0.18",
|
||||
"@types/node": "^14.14.11",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/swagger-ui-dist": "3.30.0",
|
||||
"argparse": "^1.0.10",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^7.0.9",
|
||||
"immer": "^8.0.0",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"rxjs": "^6.6.3",
|
||||
"swagger-ui-dist": "^3.33.0",
|
||||
"tslib": "^2.0.1",
|
||||
"swagger-ui-dist": "^3.37.2",
|
||||
"tslib": "^2.0.3",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
case 'value-ref': return `Reference to a state object.`;
|
||||
case 'value-ref': return `Reference to a runtime defined value.`;
|
||||
case 'data-ref': return `Reference to a computed data value.`;
|
||||
case 'text': return 'String';
|
||||
case 'interval': return `Interval [min, max]`;
|
||||
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;
|
||||
|
||||
@@ -20,8 +20,8 @@ import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import './index.html';
|
||||
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { canComputeAlphaOrbitalsOnGPU } from '../../extensions/alpha-orbitals/gpu/compute';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
interface DemoInput {
|
||||
@@ -71,7 +71,7 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
if (!canComputeAlphaOrbitalsOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Grid } from '../../mol-model/volume';
|
||||
import { SphericalBasisOrder } from './spherical-functions';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { arrayMin, arrayMax, arrayRms } from '../../mol-util/array';
|
||||
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
|
||||
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
|
||||
|
||||
// Note: generally contracted gaussians are currently not supported.
|
||||
export interface SphericalElectronShell {
|
||||
@@ -95,7 +95,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
|
||||
|
||||
const BohrToAngstromFactor = 0.529177210859;
|
||||
|
||||
export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[]) {
|
||||
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
|
||||
const boxSize = Box3D.size(Vec3(), gridInfo.box);
|
||||
const boxOrigin = Vec3.clone(gridInfo.box.min);
|
||||
|
||||
@@ -122,7 +122,7 @@ export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrd
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
*/
|
||||
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
|
||||
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
|
||||
|
||||
export function createSphericalCollocationDensityGrid(
|
||||
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
|
||||
@@ -17,9 +18,9 @@ export function createSphericalCollocationDensityGrid(
|
||||
const cubeGrid = initCubeGrid(params);
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(webgl!, cubeGrid, orbitals, ctx);
|
||||
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
throw new Error('Missing OES_texture_float WebGL extension.');
|
||||
|
||||
@@ -4,46 +4,72 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
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 quad_vert from '../../../mol-gl/shader/quad.vert';
|
||||
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
|
||||
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { arrayMin } from '../../../mol-util/array';
|
||||
import { isLittleEndian } from '../../../mol-util/is-little-endian';
|
||||
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
|
||||
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
|
||||
import shader_frag from './shader.frag';
|
||||
import { MAIN, UTILS } from './shader.frag';
|
||||
|
||||
const AlphaOrbitalsSchema = {
|
||||
...QuadSchema,
|
||||
uDimensions: UniformSpec('v3'),
|
||||
uMin: UniformSpec('v3'),
|
||||
uDelta: UniformSpec('v3'),
|
||||
const Schema = {
|
||||
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
|
||||
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
|
||||
uWidth: UniformSpec('f'),
|
||||
uNCenters: UniformSpec('i'),
|
||||
uNAlpha: UniformSpec('i'),
|
||||
uNCoeff: UniformSpec('i'),
|
||||
uMaxCoeffs: UniformSpec('i'),
|
||||
uLittleEndian: UniformSpec('b'),
|
||||
uDensity: UniformSpec('b'),
|
||||
uOccupancy: UniformSpec('f'),
|
||||
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
|
||||
};
|
||||
type AlphaOrbitalsSchema = Values<typeof AlphaOrbitalsSchema>
|
||||
const AlphaOrbitalsName = 'alpha-orbitals';
|
||||
const AlphaOrbitalsTex0 = 'alpha-orbitals-0';
|
||||
const AlphaOrbitalsTex1 = 'alpha-orbitals-1';
|
||||
const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag);
|
||||
type AlphaOrbitalsRenderable = ComputeRenderable<AlphaOrbitalsSchema>
|
||||
|
||||
const Orbitals = createGrid3dComputeRenderable({
|
||||
schema: Schema,
|
||||
loopBounds: ['uNCenters', 'uMaxCoeffs'],
|
||||
mainCode: MAIN,
|
||||
utilCode: UTILS,
|
||||
returnCode: 'v',
|
||||
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
|
||||
return createTextureData(params.grid, params.orbital);
|
||||
}
|
||||
});
|
||||
|
||||
const Density = createGrid3dComputeRenderable({
|
||||
schema: {
|
||||
...Schema,
|
||||
uOccupancy: UniformSpec('f'),
|
||||
},
|
||||
loopBounds: ['uNCenters', 'uMaxCoeffs'],
|
||||
mainCode: MAIN,
|
||||
utilCode: UTILS,
|
||||
returnCode: 'current + uOccupancy * v * v',
|
||||
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
|
||||
return {
|
||||
...createTextureData(params.grid, params.orbitals[0]),
|
||||
uOccupancy: 0
|
||||
};
|
||||
},
|
||||
cumulative: {
|
||||
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
|
||||
return params.orbitals.filter(o => o.occupancy !== 0);
|
||||
},
|
||||
update({ grid }, state: AlphaOrbital, values) {
|
||||
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
|
||||
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
|
||||
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
|
||||
return Orbitals(ctx, webgl, grid, { grid, orbital });
|
||||
}
|
||||
|
||||
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
|
||||
return Density(ctx, webgl, grid, { grid, orbitals });
|
||||
}
|
||||
|
||||
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
|
||||
const alpha = new Float32Array(alphaOrbitals.length);
|
||||
@@ -62,7 +88,7 @@ function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrde
|
||||
return alpha;
|
||||
}
|
||||
|
||||
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
|
||||
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
|
||||
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
|
||||
|
||||
let centerCount = 0;
|
||||
@@ -131,179 +157,14 @@ function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
|
||||
}
|
||||
}
|
||||
|
||||
return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff };
|
||||
}
|
||||
|
||||
function createAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
|
||||
const data = createTextureData(grid, orbital);
|
||||
|
||||
const [nx, ny, nz] = grid.dimensions;
|
||||
const width = Math.ceil(Math.sqrt(nx * ny * nz));
|
||||
|
||||
if (!ctx.namedFramebuffers[AlphaOrbitalsName]) {
|
||||
ctx.namedFramebuffers[AlphaOrbitalsName] = ctx.resources.framebuffer();
|
||||
}
|
||||
if (!ctx.namedTextures[AlphaOrbitalsTex0]) {
|
||||
ctx.namedTextures[AlphaOrbitalsTex0] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
if (!ctx.namedTextures[AlphaOrbitalsTex1]) {
|
||||
ctx.namedTextures[AlphaOrbitalsTex1] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
|
||||
const values: AlphaOrbitalsSchema = {
|
||||
...QuadValues,
|
||||
uDimensions: ValueCell.create(grid.dimensions),
|
||||
uMin: ValueCell.create(grid.box.min),
|
||||
uDelta: ValueCell.create(grid.delta),
|
||||
uWidth: ValueCell.create(width),
|
||||
uNCenters: ValueCell.create(data.nCenters),
|
||||
uNAlpha: ValueCell.create(data.nAlpha),
|
||||
uNCoeff: ValueCell.create(data.nCoeff),
|
||||
uMaxCoeffs: ValueCell.create(data.maxCoeffs),
|
||||
tCenters: ValueCell.create({ width: data.nCenters, height: 1, array: data.centers }),
|
||||
tInfo: ValueCell.create({ width: data.nCenters, height: 1, array: data.info }),
|
||||
tCoeff: ValueCell.create({ width: data.nCoeff, height: 1, array: data.coeff }),
|
||||
tAlpha: ValueCell.create({ width: data.nAlpha, height: 1, array: data.alpha }),
|
||||
uLittleEndian: ValueCell.create(isLittleEndian()),
|
||||
uDensity: ValueCell.create(false),
|
||||
uOccupancy: ValueCell.create(0),
|
||||
tCumulativeSum: ValueCell.create(ctx.namedTextures[AlphaOrbitalsTex1])
|
||||
return {
|
||||
uNCenters: centerCount,
|
||||
uNAlpha: baseCount,
|
||||
uNCoeff: coeffCount,
|
||||
uMaxCoeffs: maxCoeffs,
|
||||
tCenters: { width: centerCount, height: 1, array: centers },
|
||||
tInfo: { width: centerCount, height: 1, array: info },
|
||||
tCoeff: { width: coeffCount, height: 1, array: coeff },
|
||||
tAlpha: { width: baseCount, height: 1, array: alpha },
|
||||
};
|
||||
|
||||
const schema = { ...AlphaOrbitalsSchema };
|
||||
if (!ctx.isWebGL2) {
|
||||
// workaround for webgl1 limitation that loop counters need to be `const`
|
||||
(schema.uNCenters as any) = DefineSpec('number');
|
||||
(schema.uMaxCoeffs as any) = DefineSpec('number');
|
||||
}
|
||||
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
function getAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
|
||||
if (ctx.namedComputeRenderables[AlphaOrbitalsName]) {
|
||||
const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values as AlphaOrbitalsSchema;
|
||||
|
||||
const data = createTextureData(grid, orbital);
|
||||
|
||||
const [nx, ny, nz] = grid.dimensions;
|
||||
const width = Math.ceil(Math.sqrt(nx * ny * nz));
|
||||
|
||||
ValueCell.update(v.uDimensions, grid.dimensions);
|
||||
ValueCell.update(v.uMin, grid.box.min);
|
||||
ValueCell.update(v.uDelta, grid.delta);
|
||||
ValueCell.updateIfChanged(v.uWidth, width);
|
||||
ValueCell.updateIfChanged(v.uNCenters, data.nCenters);
|
||||
ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha);
|
||||
ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff);
|
||||
ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs);
|
||||
ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers });
|
||||
ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info });
|
||||
ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff });
|
||||
ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha });
|
||||
ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian());
|
||||
ValueCell.updateIfChanged(v.uDensity, false);
|
||||
ValueCell.updateIfChanged(v.uOccupancy, 0);
|
||||
ValueCell.updateIfChanged(v.tCumulativeSum, ctx.namedTextures[AlphaOrbitalsTex1]);
|
||||
|
||||
ctx.namedComputeRenderables[AlphaOrbitalsName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, grid, orbital);
|
||||
}
|
||||
return ctx.namedComputeRenderables[AlphaOrbitalsName];
|
||||
}
|
||||
|
||||
export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
|
||||
const [nx, ny, nz] = grid.dimensions;
|
||||
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbital);
|
||||
const width = renderable.values.uWidth.ref.value;
|
||||
|
||||
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
|
||||
webgl.namedTextures[AlphaOrbitalsTex0].define(width, width);
|
||||
webgl.namedTextures[AlphaOrbitalsTex0].attachFramebuffer(framebuffer, 'color0');
|
||||
|
||||
const { gl, state } = webgl;
|
||||
framebuffer.bind();
|
||||
gl.viewport(0, 0, width, width);
|
||||
gl.scissor(0, 0, width, width);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
renderable.render();
|
||||
|
||||
const array = new Uint8Array(width * width * 4);
|
||||
webgl.readPixels(0, 0, width, width, array);
|
||||
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
|
||||
}
|
||||
|
||||
export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) {
|
||||
return !!webgl?.extensions.textureFloat;
|
||||
}
|
||||
|
||||
export async function gpuComputeAlphaOrbitalsDensityGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[], ctx: RuntimeContext) {
|
||||
await ctx.update({ message: 'Initializing...', isIndeterminate: true });
|
||||
|
||||
const [nx, ny, nz] = grid.dimensions;
|
||||
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbitals[0]);
|
||||
const width = renderable.values.uWidth.ref.value;
|
||||
|
||||
if (!webgl.namedFramebuffers[AlphaOrbitalsName]) {
|
||||
webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
|
||||
const tex = [webgl.namedTextures[AlphaOrbitalsTex0], webgl.namedTextures[AlphaOrbitalsTex1]];
|
||||
|
||||
tex[0].define(width, width);
|
||||
tex[1].define(width, width);
|
||||
|
||||
const values = renderable.values as AlphaOrbitalsSchema;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
gl.viewport(0, 0, width, width);
|
||||
gl.scissor(0, 0, width, width);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
tex[0].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
tex[1].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
ValueCell.update(values.uDensity, true);
|
||||
|
||||
const nonZero = orbitals.filter(o => o.occupancy !== 0);
|
||||
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: nonZero.length });
|
||||
for (let i = 0; i < nonZero.length; i++) {
|
||||
const alpha = getNormalizedAlpha(grid.params.basis, nonZero[i].alpha, grid.params.sphericalOrder);
|
||||
|
||||
ValueCell.update(values.uOccupancy, nonZero[i].occupancy);
|
||||
ValueCell.update(values.tCumulativeSum, tex[(i + 1) % 2]);
|
||||
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
|
||||
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.viewport(0, 0, width, width);
|
||||
gl.scissor(0, 0, width, width);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
renderable.update();
|
||||
renderable.render();
|
||||
|
||||
if (i !== nonZero.length - 1 && ctx.shouldUpdate) {
|
||||
await ctx.update({ current: i + 1 });
|
||||
}
|
||||
}
|
||||
|
||||
const array = new Uint8Array(width * width * 4);
|
||||
webgl.readPixels(0, 0, width, width, array);
|
||||
|
||||
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
|
||||
}
|
||||
@@ -4,165 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform vec2 uQuadShift;
|
||||
uniform vec3 uDimensions;
|
||||
uniform vec3 uMin;
|
||||
uniform vec3 uDelta;
|
||||
|
||||
uniform sampler2D tCenters;
|
||||
uniform sampler2D tInfo;
|
||||
uniform sampler2D tCoeff;
|
||||
uniform sampler2D tAlpha;
|
||||
|
||||
uniform float uWidth;
|
||||
|
||||
#ifndef uNCenters
|
||||
uniform int uNCenters;
|
||||
#endif
|
||||
|
||||
uniform int uNCoeff;
|
||||
uniform int uNAlpha;
|
||||
|
||||
uniform bool uDensity;
|
||||
uniform float uOccupancy;
|
||||
uniform sampler2D tCumulativeSum;
|
||||
|
||||
uniform bool uLittleEndian;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
|
||||
// MIT License, Copyright (c) 2020 Equinor
|
||||
|
||||
float shiftRight (float v, float amt) {
|
||||
v = floor(v) + 0.5;
|
||||
return floor(v / exp2(amt));
|
||||
}
|
||||
float shiftLeft (float v, float amt) {
|
||||
return floor(v * exp2(amt) + 0.5);
|
||||
}
|
||||
float maskLast (float v, float bits) {
|
||||
return mod(v, shiftLeft(1.0, bits));
|
||||
}
|
||||
float extractBits (float num, float from, float to) {
|
||||
from = floor(from + 0.5); to = floor(to + 0.5);
|
||||
return maskLast(shiftRight(num, from), to - from);
|
||||
}
|
||||
|
||||
vec4 floatToRgba(float texelFloat) {
|
||||
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
|
||||
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
|
||||
texelFloat = abs(texelFloat);
|
||||
float exponent = floor(log2(texelFloat));
|
||||
float biased_exponent = exponent + 127.0;
|
||||
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
|
||||
float t = biased_exponent / 2.0;
|
||||
float last_bit_of_biased_exponent = fract(t) * 2.0;
|
||||
float remaining_bits_of_biased_exponent = floor(t);
|
||||
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
|
||||
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
|
||||
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
|
||||
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
|
||||
return (
|
||||
uLittleEndian
|
||||
? vec4(byte4, byte3, byte2, byte1)
|
||||
: vec4(byte1, byte2, byte3, byte4)
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
// OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
ivec4 floatsToBytes(vec4 inputFloats) {
|
||||
ivec4 bytes = ivec4(inputFloats * 255.0);
|
||||
return (
|
||||
uLittleEndian
|
||||
? bytes.abgr
|
||||
: bytes
|
||||
);
|
||||
}
|
||||
|
||||
// Break the four bytes down into an array of 32 bits.
|
||||
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
|
||||
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
|
||||
float acc = float(bytes[channelIndex]);
|
||||
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
|
||||
float powerOfTwo = exp2(float(indexInByte));
|
||||
bool bit = acc >= powerOfTwo;
|
||||
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
|
||||
acc = mod(acc, powerOfTwo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the exponent of the 32-bit float.
|
||||
float getExponent(bool bits[32]) {
|
||||
const int startIndex = 1;
|
||||
const int bitStringLength = 8;
|
||||
const int endBeforeIndex = startIndex + bitStringLength;
|
||||
float acc = 0.0;
|
||||
int pow2 = bitStringLength - 1;
|
||||
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
|
||||
acc += float(bits[bitIndex]) * exp2(float(pow2--));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Compute the mantissa of the 32-bit float.
|
||||
float getMantissa(bool bits[32], bool subnormal) {
|
||||
const int startIndex = 9;
|
||||
const int bitStringLength = 23;
|
||||
const int endBeforeIndex = startIndex + bitStringLength;
|
||||
// Leading/implicit/hidden bit convention:
|
||||
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
|
||||
float acc = float(!subnormal) * exp2(float(bitStringLength));
|
||||
int pow2 = bitStringLength - 1;
|
||||
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
|
||||
acc += float(bits[bitIndex]) * exp2(float(pow2--));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Parse the float from its 32 bits.
|
||||
float bitsToFloat(bool bits[32]) {
|
||||
float signBit = float(bits[0]) * -2.0 + 1.0;
|
||||
float exponent = getExponent(bits);
|
||||
bool subnormal = abs(exponent - 0.0) < 0.01;
|
||||
float mantissa = getMantissa(bits, subnormal);
|
||||
float exponentBias = 127.0;
|
||||
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
|
||||
}
|
||||
|
||||
float rgbaToFloat(vec4 texelRGBA) {
|
||||
ivec4 rgbaBytes = floatsToBytes(texelRGBA);
|
||||
bool bits[32];
|
||||
bytesToBits(rgbaBytes, bits);
|
||||
return bitsToFloat(bits);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
export const UTILS = `
|
||||
float L1(vec3 p, float a0, float a1, float a2) {
|
||||
return a0 * p.z + a1 * p.x + a2 * p.y;
|
||||
}
|
||||
@@ -213,12 +55,10 @@ float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, flo
|
||||
}
|
||||
|
||||
float alpha(float offset, float f) {
|
||||
#ifdef uMaxCoeffs
|
||||
#ifdef WEBGL1
|
||||
// in webgl1, the value is in the alpha channel!
|
||||
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
|
||||
#endif
|
||||
|
||||
#ifndef uMaxCoeffs
|
||||
#else
|
||||
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
|
||||
#endif
|
||||
}
|
||||
@@ -249,7 +89,7 @@ float Y(int L, vec3 X, float aO, float fA) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#ifndef uMaxCoeffs
|
||||
#ifndef WEBGL1
|
||||
float R(float R2, int start, int end, float fCoeff) {
|
||||
float gauss = 0.0;
|
||||
for (int i = start; i < end; i++) {
|
||||
@@ -258,9 +98,7 @@ float Y(int L, vec3 X, float aO, float fA) {
|
||||
}
|
||||
return gauss;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef uMaxCoeffs
|
||||
#else
|
||||
float R(float R2, int start, int end, float fCoeff) {
|
||||
float gauss = 0.0;
|
||||
int o = start;
|
||||
@@ -274,28 +112,13 @@ float Y(int L, vec3 X, float aO, float fA) {
|
||||
return gauss;
|
||||
}
|
||||
#endif
|
||||
`;
|
||||
|
||||
float intDiv(float a, float b) { return float(int(a) / int(b)); }
|
||||
float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
|
||||
|
||||
void main(void) {
|
||||
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
|
||||
|
||||
// axis order fast to slow Z, Y, X
|
||||
// TODO: support arbitrary axis orders?
|
||||
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
|
||||
float j = intMod(kk, uDimensions.y);
|
||||
float i = intDiv(kk, uDimensions.y);
|
||||
|
||||
vec3 xyz = uMin + uDelta * vec3(i, j, k);
|
||||
|
||||
export const MAIN = `
|
||||
float fCenter = 1.0 / float(uNCenters - 1);
|
||||
float fCoeff = 1.0 / float(uNCoeff - 1);
|
||||
float fA = 1.0 / float(uNAlpha - 1);
|
||||
|
||||
// gl_FragColor = floatToRgba(offset);
|
||||
// return;
|
||||
|
||||
float v = 0.0;
|
||||
|
||||
for (int i = 0; i < uNCenters; i++) {
|
||||
@@ -319,13 +142,4 @@ void main(void) {
|
||||
|
||||
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
|
||||
}
|
||||
|
||||
|
||||
if (uDensity) {
|
||||
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)));
|
||||
gl_FragColor = floatToRgba(current + uOccupancy * v * v);
|
||||
} else {
|
||||
gl_FragColor = floatToRgba(v);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -7,11 +7,14 @@
|
||||
*/
|
||||
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { sphericalCollocation } from './collocation';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
|
||||
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
|
||||
|
||||
// setDebugMode(true);
|
||||
|
||||
export function createSphericalCollocationGrid(
|
||||
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
|
||||
@@ -20,9 +23,9 @@ export function createSphericalCollocationGrid(
|
||||
const cubeGrid = initCubeGrid(params);
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cubeGrid, orbital);
|
||||
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
// console.time('cpu');
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Theme } from '../../mol-theme/theme';
|
||||
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
|
||||
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
|
||||
import { createSphericalCollocationDensityGrid } from './density';
|
||||
import { Tensor } from '../../mol-math/linear-algebra';
|
||||
|
||||
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
|
||||
|
||||
@@ -49,9 +50,43 @@ const CreateOrbitalVolumeParamBase = {
|
||||
{ atomCount: 25, spacing: 0.4 },
|
||||
{ atomCount: 0, spacing: 0.35 },
|
||||
]
|
||||
}),
|
||||
clampValues: PD.MappedStatic('off', {
|
||||
off: PD.EmptyGroup(),
|
||||
on: PD.Group({
|
||||
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
function clampData(matrix: Tensor.Data, min: number, max: number) {
|
||||
for (let i = 0, _i = matrix.length; i < _i; i++) {
|
||||
const v = matrix[i];
|
||||
if (v < min) matrix[i] = min;
|
||||
else if (v > max) matrix[i] = max;
|
||||
}
|
||||
}
|
||||
|
||||
function clampGrid(data: CubeGrid, v: number) {
|
||||
const grid = data.grid;
|
||||
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
|
||||
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
|
||||
|
||||
// clamp values for better direct volume resolution
|
||||
// current implementation uses Byte array for values
|
||||
// if this is not enough, update mol* to use float
|
||||
// textures instead
|
||||
if (grid.stats.min < min || grid.stats.max > max) {
|
||||
clampData(data.grid.cells.data, min, max);
|
||||
if (grid.stats.min < min) {
|
||||
(grid.stats.min as number) = min;
|
||||
}
|
||||
if (grid.stats.max > max) {
|
||||
(grid.stats.max as number) = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
|
||||
name: 'create-orbital-volume',
|
||||
display: 'Orbital Volume',
|
||||
@@ -84,6 +119,10 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
|
||||
if (params.clampValues?.name === 'on') {
|
||||
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
|
||||
}
|
||||
|
||||
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
|
||||
});
|
||||
}
|
||||
@@ -112,6 +151,10 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
|
||||
if (params.clampValues?.name === 'on') {
|
||||
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
|
||||
}
|
||||
|
||||
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
|
||||
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
|
||||
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
|
||||
import { MembraneOrientation } from './prop';
|
||||
import { ThemeRegistryContext } from '../../mol-theme/theme';
|
||||
@@ -27,6 +26,7 @@ import { MembraneOrientationProvider } from './prop';
|
||||
import { MarkerActions } from '../../mol-util/marker-action';
|
||||
import { lociLabel } from '../../mol-theme/label';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
|
||||
const SharedParams = {
|
||||
color: PD.Color(ColorNames.lightgrey),
|
||||
@@ -61,7 +61,6 @@ export type BilayerRimsParams = typeof BilayerRimsParams
|
||||
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
|
||||
|
||||
const MembraneOrientationVisuals = {
|
||||
'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
|
||||
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
|
||||
};
|
||||
@@ -91,9 +90,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation
|
||||
factory: MembraneOrientationRepresentation,
|
||||
getParams: getMembraneOrientationParams,
|
||||
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
|
||||
defaultColorTheme: { name: 'uniform' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0
|
||||
defaultColorTheme: { name: 'shape-group' },
|
||||
defaultSizeTheme: { name: 'shape-group' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => MembraneOrientationProvider.ref(data, false)
|
||||
}
|
||||
});
|
||||
|
||||
function membraneLabel(data: Structure) {
|
||||
@@ -151,28 +154,3 @@ function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal
|
||||
MeshBuilder.addPrimitive(state, Mat4.id, circle);
|
||||
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
|
||||
}
|
||||
|
||||
function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> {
|
||||
const { density } = props;
|
||||
const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!;
|
||||
const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius);
|
||||
|
||||
const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry);
|
||||
getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius);
|
||||
getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius);
|
||||
return Shape.create('Bilayer spheres', data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data));
|
||||
}
|
||||
|
||||
function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) {
|
||||
Vec3.normalize(normalVector, normalVector);
|
||||
const d = -Vec3.dot(normalVector, point);
|
||||
const rep = Vec3();
|
||||
for (let i = -1000, il = 1000; i < il; i += density) {
|
||||
for (let j = -1000, jl = 1000; j < jl; j += density) {
|
||||
Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]);
|
||||
if (Vec3.squaredDistance(rep, point) < sqRadius) {
|
||||
spheresBuilder.add(rep[0], rep[1], rep[2], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,8 +209,8 @@ namespace Camera {
|
||||
up: Vec3.create(0, 1, 0),
|
||||
target: Vec3.create(0, 0, 0),
|
||||
|
||||
radius: 10,
|
||||
radiusMax: 10,
|
||||
radius: 0,
|
||||
radiusMax: 0,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -47,7 +47,7 @@ export const Canvas3DParams = {
|
||||
on: PD.Group(StereoCameraParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
|
||||
manualReset: PD.Boolean(false, { isHidden: true })
|
||||
manualReset: PD.Boolean(false, { isHidden: true }),
|
||||
}, { pivot: 'mode' }),
|
||||
cameraFog: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
@@ -85,6 +85,110 @@ export type PartialCanvas3DProps = {
|
||||
[K in keyof Canvas3DProps]?: Canvas3DProps[K] extends { name: string, params: any } ? Canvas3DProps[K] : Partial<Canvas3DProps[K]>
|
||||
}
|
||||
|
||||
export { Canvas3DContext };
|
||||
|
||||
/** Can be used to create multiple Canvas3D objects */
|
||||
interface Canvas3DContext {
|
||||
readonly canvas: HTMLCanvasElement
|
||||
readonly webgl: WebGLContext
|
||||
readonly input: InputObserver
|
||||
readonly passes: Passes
|
||||
readonly attribs: Readonly<Canvas3DContext.Attribs>
|
||||
readonly contextLost: BehaviorSubject<now.Timestamp>
|
||||
readonly contextRestored: BehaviorSubject<now.Timestamp>
|
||||
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
|
||||
}
|
||||
|
||||
namespace Canvas3DContext {
|
||||
const DefaultAttribs = {
|
||||
/** true by default to avoid issues with Safari (Jan 2021) */
|
||||
antialias: true,
|
||||
/** true to support multiple Canvas3D objects with a single context */
|
||||
preserveDrawingBuffer: true,
|
||||
pixelScale: 1,
|
||||
pickScale: 0.25,
|
||||
enableWboit: true
|
||||
};
|
||||
export type Attribs = typeof DefaultAttribs
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
|
||||
const a = { ...DefaultAttribs, ...attribs };
|
||||
const { antialias, preserveDrawingBuffer, pixelScale } = a;
|
||||
const gl = getGLContext(canvas, {
|
||||
antialias,
|
||||
preserveDrawingBuffer,
|
||||
alpha: true, // the renderer requires an alpha channel
|
||||
depth: true, // the renderer requires a depth buffer
|
||||
premultipliedAlpha: true, // the renderer outputs PMA
|
||||
});
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context');
|
||||
|
||||
const input = InputObserver.fromElement(canvas, { pixelScale });
|
||||
const webgl = createContext(gl, { pixelScale });
|
||||
const passes = new Passes(webgl, attribs);
|
||||
|
||||
if (isDebugMode) {
|
||||
const loseContextExt = gl.getExtension('WEBGL_lose_context');
|
||||
if (loseContextExt) {
|
||||
// Hold down shift+ctrl+alt and press any mouse button to call `loseContext`.
|
||||
// After 1 second `restoreContext` will be called.
|
||||
canvas.addEventListener('mousedown', e => {
|
||||
if (webgl.isContextLost) return;
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
|
||||
|
||||
if (isDebugMode) console.log('lose context');
|
||||
loseContextExt.loseContext();
|
||||
|
||||
setTimeout(() => {
|
||||
if (!webgl.isContextLost) return;
|
||||
if (isDebugMode) console.log('restore context');
|
||||
loseContextExt.restoreContext();
|
||||
}, 1000);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.khronos.org/webgl/wiki/HandlingContextLost
|
||||
|
||||
const contextLost = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
|
||||
const handleWebglContextLost = (e: Event) => {
|
||||
webgl.setContextLost();
|
||||
e.preventDefault();
|
||||
if (isDebugMode) console.log('context lost');
|
||||
contextLost.next(now());
|
||||
};
|
||||
|
||||
const handlewWebglContextRestored = () => {
|
||||
if (!webgl.isContextLost) return;
|
||||
webgl.handleContextRestored(() => {
|
||||
passes.draw.reset();
|
||||
});
|
||||
if (isDebugMode) console.log('context restored');
|
||||
};
|
||||
|
||||
canvas.addEventListener('webglcontextlost', handleWebglContextLost, false);
|
||||
canvas.addEventListener('webglcontextrestored', handlewWebglContextRestored, false);
|
||||
|
||||
return {
|
||||
canvas,
|
||||
webgl,
|
||||
input,
|
||||
passes,
|
||||
attribs: a,
|
||||
contextLost,
|
||||
contextRestored: webgl.contextRestored,
|
||||
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
|
||||
input.dispose();
|
||||
|
||||
canvas.removeEventListener('webglcontextlost', handleWebglContextLost, false);
|
||||
canvas.removeEventListener('webglcontextrestored', handlewWebglContextRestored, false);
|
||||
webgl.destroy(options);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { Canvas3D };
|
||||
|
||||
interface Canvas3D {
|
||||
@@ -117,10 +221,13 @@ interface Canvas3D {
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
readonly didDraw: BehaviorSubject<now.Timestamp>
|
||||
readonly commited: BehaviorSubject<now.Timestamp>
|
||||
readonly reprCount: BehaviorSubject<number>
|
||||
readonly resized: BehaviorSubject<any>
|
||||
|
||||
handleResize(): void
|
||||
/** performs handleResize on the next animation frame */
|
||||
requestResize(): void
|
||||
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
|
||||
readonly camera: Camera
|
||||
@@ -149,58 +256,7 @@ namespace Canvas3D {
|
||||
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 function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number, enableWboit: boolean }> = {}) {
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: true,
|
||||
antialias: (attribs.antialias ?? true) && !attribs.enableWboit,
|
||||
depth: true,
|
||||
preserveDrawingBuffer: true,
|
||||
premultipliedAlpha: true,
|
||||
});
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context');
|
||||
|
||||
const { pixelScale } = attribs;
|
||||
const input = InputObserver.fromElement(canvas, { pixelScale });
|
||||
const webgl = createContext(gl, { pixelScale });
|
||||
const passes = new Passes(webgl, attribs);
|
||||
|
||||
if (isDebugMode) {
|
||||
const loseContextExt = gl.getExtension('WEBGL_lose_context');
|
||||
if (loseContextExt) {
|
||||
canvas.addEventListener('mousedown', e => {
|
||||
if (webgl.isContextLost) return;
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
|
||||
|
||||
if (isDebugMode) console.log('lose context');
|
||||
loseContextExt.loseContext();
|
||||
|
||||
setTimeout(() => {
|
||||
if (!webgl.isContextLost) return;
|
||||
if (isDebugMode) console.log('restore context');
|
||||
loseContextExt.restoreContext();
|
||||
}, 1000);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.khronos.org/webgl/wiki/HandlingContextLost
|
||||
|
||||
canvas.addEventListener('webglcontextlost', e => {
|
||||
webgl.setContextLost();
|
||||
e.preventDefault();
|
||||
if (isDebugMode) console.log('context lost');
|
||||
}, false);
|
||||
|
||||
canvas.addEventListener('webglcontextrestored', () => {
|
||||
if (!webgl.isContextLost) return;
|
||||
webgl.handleContextRestored();
|
||||
if (isDebugMode) console.log('context restored');
|
||||
}, false);
|
||||
|
||||
return create(webgl, input, passes, props, { pixelScale });
|
||||
}
|
||||
|
||||
export function create(webgl: WebGLContext, input: InputObserver, passes: Passes, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ pixelScale: number }>): Canvas3D {
|
||||
export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
|
||||
@@ -209,6 +265,7 @@ namespace Canvas3D {
|
||||
|
||||
let startTime = now();
|
||||
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
|
||||
const { gl, contextRestored } = webgl;
|
||||
|
||||
@@ -240,6 +297,7 @@ namespace Canvas3D {
|
||||
let cameraResetRequested = false;
|
||||
let nextCameraResetDuration: number | undefined = void 0;
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
|
||||
let resizeRequested = false;
|
||||
|
||||
let notifyDidDraw = true;
|
||||
|
||||
@@ -282,6 +340,12 @@ namespace Canvas3D {
|
||||
|
||||
function render(force: boolean) {
|
||||
if (webgl.isContextLost) return false;
|
||||
|
||||
if (resizeRequested) {
|
||||
handleResize(false);
|
||||
resizeRequested = false;
|
||||
}
|
||||
|
||||
if (x > gl.drawingBufferWidth || x + width < 0 ||
|
||||
y > gl.drawingBufferHeight || y + height < 0
|
||||
) return false;
|
||||
@@ -382,6 +446,7 @@ namespace Canvas3D {
|
||||
draw(true);
|
||||
forceDrawAfterAllCommited = false;
|
||||
}
|
||||
commited.next(now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,6 +522,13 @@ namespace Canvas3D {
|
||||
materialId: r.materialId,
|
||||
})));
|
||||
console.log(webgl.stats);
|
||||
|
||||
const { texture, attribute, elements } = webgl.resources.getByteCounts();
|
||||
console.log({
|
||||
texture: `${(texture / 1024 / 1024).toFixed(3)} MiB`,
|
||||
attribute: `${(attribute / 1024 / 1024).toFixed(3)} MiB`,
|
||||
elements: `${(elements / 1024 / 1024).toFixed(3)} MiB`,
|
||||
});
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
@@ -541,10 +613,22 @@ namespace Canvas3D {
|
||||
const contextRestoredSub = contextRestored.subscribe(() => {
|
||||
pickHelper.dirty = true;
|
||||
draw(true);
|
||||
// Unclear why, but in Chrome with wboit enabled the first `draw` only clears
|
||||
// the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
|
||||
// context loss so it is unclear if it behaves the same.
|
||||
draw(true);
|
||||
});
|
||||
|
||||
const resized = new BehaviorSubject<any>(0);
|
||||
|
||||
function handleResize(draw = true) {
|
||||
passes.updateSize();
|
||||
updateViewport();
|
||||
syncViewport();
|
||||
if (draw) requestDraw(true);
|
||||
resized.next(+new Date());
|
||||
}
|
||||
|
||||
return {
|
||||
webgl,
|
||||
|
||||
@@ -590,12 +674,9 @@ namespace Canvas3D {
|
||||
mark,
|
||||
getLoci,
|
||||
|
||||
handleResize: () => {
|
||||
passes.updateSize();
|
||||
updateViewport();
|
||||
syncViewport();
|
||||
requestDraw(true);
|
||||
resized.next(+new Date());
|
||||
handleResize,
|
||||
requestResize: () => {
|
||||
resizeRequested = true;
|
||||
},
|
||||
requestCameraReset: options => {
|
||||
nextCameraResetDuration = options?.durationMs;
|
||||
@@ -607,6 +688,7 @@ namespace Canvas3D {
|
||||
get notifyDidDraw() { return notifyDidDraw; },
|
||||
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
|
||||
didDraw,
|
||||
commited,
|
||||
reprCount,
|
||||
resized,
|
||||
setProps: (properties, doNotRequestDraw = false) => {
|
||||
@@ -688,7 +770,6 @@ namespace Canvas3D {
|
||||
|
||||
scene.clear();
|
||||
helper.debug.clear();
|
||||
input.dispose();
|
||||
controls.dispose();
|
||||
renderer.dispose();
|
||||
interactionHelper.dispose();
|
||||
|
||||
@@ -160,5 +160,5 @@ const instanceMaterialId = getNextMaterialId();
|
||||
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false }, materialId);
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
|
||||
}
|
||||
@@ -70,6 +70,7 @@ export class CameraHelper {
|
||||
this.scene.clear();
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
|
||||
this.renderObject = createAxesRenderObject(params);
|
||||
this.renderObject.state.noClip = true;
|
||||
this.scene.add(this.renderObject);
|
||||
this.scene.commit();
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export class HandleHelper {
|
||||
this.scene.clear();
|
||||
const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
|
||||
this.renderObject = createHandleRenderObject(params);
|
||||
this.renderObject.state.noClip = true;
|
||||
this.scene.add(this.renderObject);
|
||||
this.scene.commit();
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ export class DrawPass {
|
||||
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.wboit?.reset();
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const w = this.colorTarget.getWidth();
|
||||
const h = this.colorTarget.getHeight();
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -69,11 +69,13 @@ export class MultiSamplePass {
|
||||
private compose: ComposeRenderable
|
||||
|
||||
constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) {
|
||||
const { colorBufferFloat, textureFloat } = webgl.extensions;
|
||||
const { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } = webgl.extensions;
|
||||
const width = drawPass.colorTarget.getWidth();
|
||||
const height = drawPass.colorTarget.getHeight();
|
||||
this.colorTarget = webgl.createRenderTarget(width, height, false);
|
||||
this.composeTarget = webgl.createRenderTarget(width, height, false, colorBufferFloat && textureFloat ? 'float32' : 'uint8');
|
||||
const type = colorBufferHalfFloat && textureHalfFloat ? 'fp16' :
|
||||
colorBufferFloat && textureFloat ? 'float32' : 'uint8';
|
||||
this.composeTarget = webgl.createRenderTarget(width, height, false, type);
|
||||
this.holdTarget = webgl.createRenderTarget(width, height, false);
|
||||
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
|
||||
}
|
||||
@@ -193,7 +195,7 @@ export class MultiSamplePass {
|
||||
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
const sampleWeight = 1.0 / offsetList.length;
|
||||
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing) || props.postprocessing.antialiasing.name === 'on';
|
||||
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
|
||||
|
||||
if (sampleIndex === -1) {
|
||||
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
|
||||
|
||||
@@ -110,7 +110,7 @@ export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
|
||||
|
||||
export class PostprocessingPass {
|
||||
static isEnabled(props: PostprocessingProps) {
|
||||
return props.occlusion.name === 'on' || props.outline.name === 'on';
|
||||
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'on';
|
||||
}
|
||||
|
||||
readonly target: RenderTarget
|
||||
@@ -146,7 +146,7 @@ export class PostprocessingPass {
|
||||
private updateState(camera: ICamera) {
|
||||
const { gl, state } = this.webgl;
|
||||
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
|
||||
@@ -89,13 +89,37 @@ export class WboitPass {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
const { resources, extensions } = webgl;
|
||||
const { drawBuffers, textureFloat, colorBufferFloat, depthTexture } = extensions;
|
||||
reset() {
|
||||
if (this._enabled) this._init();
|
||||
}
|
||||
|
||||
private _init() {
|
||||
const { extensions: { drawBuffers } } = this.webgl;
|
||||
|
||||
this.framebuffer.bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1,
|
||||
]);
|
||||
|
||||
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
|
||||
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, depthTexture } } = webgl;
|
||||
if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers) {
|
||||
if (isDebugMode) console.log('Missing extensions required for "wboit"');
|
||||
return;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!WboitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources } = webgl;
|
||||
|
||||
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureA.define(width, height);
|
||||
@@ -104,17 +128,8 @@ export class WboitPass {
|
||||
this.textureB.define(width, height);
|
||||
|
||||
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
|
||||
|
||||
this.framebuffer = resources.framebuffer();
|
||||
this.framebuffer.bind();
|
||||
drawBuffers.drawBuffers([
|
||||
drawBuffers.COLOR_ATTACHMENT0,
|
||||
drawBuffers.COLOR_ATTACHMENT1,
|
||||
]);
|
||||
|
||||
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
|
||||
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
|
||||
|
||||
this._init();
|
||||
this._enabled = true;
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ export namespace BaseGeometry {
|
||||
colorOnly: false,
|
||||
opaque,
|
||||
writeDepth: opaque,
|
||||
noClip: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Cage } from '../../../mol-geo/primitive/cage';
|
||||
|
||||
export interface LinesBuilder {
|
||||
add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number): void
|
||||
addVec(start: Vec3, end: Vec3, group: number): void
|
||||
addFixedCountDashes(start: Vec3, end: Vec3, segmentCount: number, group: number): void
|
||||
addFixedLengthDashes(start: Vec3, end: Vec3, segmentLength: number, group: number): void
|
||||
addCage(t: Mat4, cage: Cage, group: number): void
|
||||
@@ -39,6 +40,14 @@ export namespace LinesBuilder {
|
||||
}
|
||||
};
|
||||
|
||||
const addVec = (start: Vec3, end: Vec3, group: number) => {
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
caAdd3(starts, start[0], start[1], start[2]);
|
||||
caAdd3(ends, end[0], end[1], end[2]);
|
||||
caAdd(groups, group);
|
||||
}
|
||||
};
|
||||
|
||||
const addFixedCountDashes = (start: Vec3, end: Vec3, segmentCount: number, group: number) => {
|
||||
const d = Vec3.distance(start, end);
|
||||
const s = Math.floor(segmentCount / 2);
|
||||
@@ -57,6 +66,7 @@ export namespace LinesBuilder {
|
||||
|
||||
return {
|
||||
add,
|
||||
addVec,
|
||||
addFixedCountDashes,
|
||||
addFixedLengthDashes: (start: Vec3, end: Vec3, segmentLength: number, group: number) => {
|
||||
const d = Vec3.distance(start, end);
|
||||
|
||||
@@ -75,6 +75,8 @@ export namespace TextureMesh {
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -102,7 +104,8 @@ export namespace TextureMesh {
|
||||
|
||||
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount / 3, groupCount, instanceCount };
|
||||
|
||||
const transformBoundingSphere = calculateTransformBoundingSphere(textureMesh.boundingSphere, transform.aTransform.ref.value, transform.instanceCount.ref.value);
|
||||
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
uGeoTexDim: textureMesh.geoTextureDim,
|
||||
@@ -111,9 +114,9 @@ export namespace TextureMesh {
|
||||
|
||||
// aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
|
||||
aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount))),
|
||||
boundingSphere: ValueCell.create(transformBoundingSphere),
|
||||
invariantBoundingSphere: ValueCell.create(Sphere3D.clone(textureMesh.boundingSphere)),
|
||||
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(textureMesh.boundingSphere)),
|
||||
boundingSphere: ValueCell.create(boundingSphere),
|
||||
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
|
||||
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
|
||||
|
||||
...color,
|
||||
...marker,
|
||||
@@ -126,6 +129,8 @@ export namespace TextureMesh {
|
||||
dDoubleSided: ValueCell.create(props.doubleSided),
|
||||
dFlatShaded: ValueCell.create(props.flatShaded),
|
||||
dFlipSided: ValueCell.create(props.flipSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded),
|
||||
dGeoTexture: ValueCell.create(true),
|
||||
};
|
||||
}
|
||||
@@ -137,11 +142,12 @@ export namespace TextureMesh {
|
||||
}
|
||||
|
||||
function updateValues(values: TextureMeshValues, props: PD.Values<Params>) {
|
||||
ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
|
||||
|
||||
BaseGeometry.updateValues(values, props);
|
||||
ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
|
||||
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
|
||||
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
|
||||
if (values.drawCount.ref.value > values.aGroup.ref.value.length) {
|
||||
// console.log('updating vertex ids in aGroup to handle larger drawCount')
|
||||
@@ -150,8 +156,9 @@ export namespace TextureMesh {
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
|
||||
const invariantBoundingSphere = textureMesh.boundingSphere;
|
||||
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ function createPoints() {
|
||||
pickable: true,
|
||||
colorOnly: false,
|
||||
opaque: true,
|
||||
writeDepth: true
|
||||
writeDepth: true,
|
||||
noClip: false,
|
||||
};
|
||||
|
||||
return createRenderObject('points', values, state, -1);
|
||||
|
||||
223
src/mol-gl/compute/grid3d.ts
Normal file
223
src/mol-gl/compute/grid3d.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common';
|
||||
import shader_template from '../shader/util/grid3d-template.frag';
|
||||
import quad_vert from '../shader/quad.vert';
|
||||
import { ShaderCode } from '../shader-code';
|
||||
import { UUID, ValueCell } from '../../mol-util';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform';
|
||||
import { QuadSchema, QuadValues } from './util';
|
||||
import { createComputeRenderItem } from '../webgl/render-item';
|
||||
import { createComputeRenderable } from '../renderable';
|
||||
import { isLittleEndian } from '../../mol-util/is-little-endian';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
export function canComputeGrid3dOnGPU(webgl?: WebGLContext) {
|
||||
return !!webgl?.extensions.textureFloat;
|
||||
}
|
||||
|
||||
export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> {
|
||||
schema: S,
|
||||
// indicate which params are loop bounds for WebGL1 compat
|
||||
loopBounds?: (keyof S)[]
|
||||
utilCode?: string,
|
||||
mainCode: string,
|
||||
returnCode: string,
|
||||
|
||||
values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
|
||||
|
||||
cumulative?: {
|
||||
states(params: P): CS[],
|
||||
update(params: P, state: CS, values: Values<S>): void,
|
||||
// call gl.readPixes every 'yieldPeriod' states to split the computation
|
||||
// into multiple parts, if not set, the computation will be synchronous
|
||||
yieldPeriod?: number
|
||||
}
|
||||
}
|
||||
|
||||
const FrameBufferName = 'grid3d-computable' as const;
|
||||
const Texture0Name = 'grid3d-computable-0' as const;
|
||||
const Texture1Name = 'grid3d-computable-1' as const;
|
||||
|
||||
const SchemaBase = {
|
||||
...QuadSchema,
|
||||
uDimensions: UniformSpec('v3'),
|
||||
uMin: UniformSpec('v3'),
|
||||
uDelta: UniformSpec('v3'),
|
||||
uWidth: UniformSpec('f'),
|
||||
uLittleEndian: UniformSpec('b'),
|
||||
};
|
||||
|
||||
const CumulativeSumSchema = {
|
||||
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
|
||||
};
|
||||
|
||||
export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
|
||||
const id = UUID.create22();
|
||||
|
||||
const uniforms: string[] = [];
|
||||
|
||||
objectForEach(spec.schema, (u, k) => {
|
||||
if (u.type === 'define') return;
|
||||
if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported');
|
||||
const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0;
|
||||
if (isBound) uniforms.push(`#ifndef ${k}`);
|
||||
if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`);
|
||||
else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`);
|
||||
if (isBound) uniforms.push(`#endif`);
|
||||
});
|
||||
|
||||
const code = shader_template
|
||||
.replace('{UNIFORMS}', uniforms.join('\n'))
|
||||
.replace('{UTILS}', spec.utilCode ?? '')
|
||||
.replace('{MAIN}', spec.mainCode)
|
||||
.replace('{RETURN}', spec.returnCode);
|
||||
|
||||
const shader = ShaderCode(id, quad_vert, code);
|
||||
|
||||
return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => {
|
||||
const schema: RenderableSchema = {
|
||||
...SchemaBase,
|
||||
...(spec.cumulative ? CumulativeSumSchema : {}),
|
||||
...spec.schema,
|
||||
};
|
||||
|
||||
if (!webgl.isWebGL2) {
|
||||
if (spec.loopBounds) {
|
||||
for (const b of spec.loopBounds) {
|
||||
(schema as any)[b] = DefineSpec('number');
|
||||
}
|
||||
}
|
||||
(schema as any)['WEBGL1'] = DefineSpec('boolean');
|
||||
}
|
||||
|
||||
if (spec.cumulative) {
|
||||
(schema as any)['CUMULATIVE'] = DefineSpec('boolean');
|
||||
}
|
||||
|
||||
if (!webgl.namedFramebuffers[FrameBufferName]) {
|
||||
webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[FrameBufferName];
|
||||
|
||||
if (!webgl.namedTextures[Texture0Name]) {
|
||||
webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
if (spec.cumulative && !webgl.namedTextures[Texture1Name]) {
|
||||
webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
|
||||
const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]];
|
||||
|
||||
const [nx, ny, nz] = grid.dimensions;
|
||||
const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
|
||||
|
||||
const values: UnboxedValues<S & typeof SchemaBase> = {
|
||||
uDimensions: grid.dimensions,
|
||||
uMin: grid.box.min,
|
||||
uDelta: getRegularGrid3dDelta(grid),
|
||||
uWidth,
|
||||
uLittleEndian: isLittleEndian(),
|
||||
...spec.values(params, grid)
|
||||
} as any;
|
||||
|
||||
if (!webgl.isWebGL2) {
|
||||
(values as any).WEBGL1 = true;
|
||||
}
|
||||
if (spec.cumulative) {
|
||||
(values as any).tCumulativeSum = tex[0];
|
||||
(values as any).CUMULATIVE = true;
|
||||
}
|
||||
|
||||
let renderable = webgl.namedComputeRenderables[id];
|
||||
let cells: RenderableValues;
|
||||
if (renderable) {
|
||||
cells = renderable.values as RenderableValues;
|
||||
objectForEach(values, (c, k) => {
|
||||
const s = schema[k];
|
||||
if (s?.type === 'value' || s?.type === 'attribute') return;
|
||||
|
||||
if (!s || !isUniformValueScalar(s.kind as any)) {
|
||||
ValueCell.update(cells[k], c);
|
||||
} else {
|
||||
ValueCell.updateIfChanged(cells[k], c);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cells = {} as any;
|
||||
objectForEach(QuadValues, (v, k) => (cells as any)[k] = v);
|
||||
objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v));
|
||||
renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells);
|
||||
}
|
||||
|
||||
const array = new Uint8Array(uWidth * uWidth * 4);
|
||||
if (spec.cumulative) {
|
||||
const { gl } = webgl;
|
||||
|
||||
const states = spec.cumulative.states(params);
|
||||
|
||||
tex[0].define(uWidth, uWidth);
|
||||
tex[1].define(uWidth, uWidth);
|
||||
|
||||
resetGl(webgl, uWidth);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
tex[0].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
tex[1].attachFramebuffer(framebuffer, 'color0');
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
if (spec.cumulative.yieldPeriod) {
|
||||
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
|
||||
}
|
||||
|
||||
const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
|
||||
|
||||
for (let i = 0; i < states.length; i++) {
|
||||
ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
|
||||
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
|
||||
resetGl(webgl, uWidth);
|
||||
spec.cumulative.update(params, states[i], cells as any);
|
||||
renderable.update();
|
||||
renderable.render();
|
||||
|
||||
if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
|
||||
if (i % yieldPeriod === yieldPeriod - 1) {
|
||||
webgl.readPixels(0, 0, 1, 1, array);
|
||||
}
|
||||
if (ctx.shouldUpdate) {
|
||||
await ctx.update({ current: i + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tex[0].define(uWidth, uWidth);
|
||||
tex[0].attachFramebuffer(framebuffer, 'color0');
|
||||
framebuffer.bind();
|
||||
resetGl(webgl, uWidth);
|
||||
renderable.update();
|
||||
renderable.render();
|
||||
}
|
||||
|
||||
webgl.readPixels(0, 0, uWidth, uWidth, array);
|
||||
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
|
||||
};
|
||||
}
|
||||
|
||||
function resetGl(webgl: WebGLContext, w: number) {
|
||||
const { gl, state } = webgl;
|
||||
gl.viewport(0, 0, w, w);
|
||||
gl.scissor(0, 0, w, w);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
|
||||
import { ShaderCode } from '../../../mol-gl/shader-code';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { QuadSchema, QuadValues } from '../util';
|
||||
import { Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { Vec2, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { getHistopyramidSum } from './sum';
|
||||
import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
|
||||
import { isPowerOfTwo } from '../../../mol-math/misc';
|
||||
@@ -24,39 +24,48 @@ const HistopyramidReductionSchema = {
|
||||
tPreviousLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uSize: UniformSpec('f'),
|
||||
uTexSize: UniformSpec('f'),
|
||||
uFirst: UniformSpec('b'),
|
||||
};
|
||||
|
||||
let HistopyramidReductionRenderable: ComputeRenderable<Values<typeof HistopyramidReductionSchema>>;
|
||||
const HistogramPyramidName = 'histogram-pyramid';
|
||||
|
||||
function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
|
||||
if (HistopyramidReductionRenderable) {
|
||||
ValueCell.update(HistopyramidReductionRenderable.values.tPreviousLevel, initialTexture);
|
||||
HistopyramidReductionRenderable.update();
|
||||
return HistopyramidReductionRenderable;
|
||||
if (ctx.namedComputeRenderables[HistogramPyramidName]) {
|
||||
const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
|
||||
|
||||
ValueCell.update(v.tPreviousLevel, initialTexture);
|
||||
|
||||
ctx.namedComputeRenderables[HistogramPyramidName].update();
|
||||
} else {
|
||||
const values: Values<typeof HistopyramidReductionSchema> = {
|
||||
...QuadValues,
|
||||
tPreviousLevel: ValueCell.create(initialTexture),
|
||||
uSize: ValueCell.create(0),
|
||||
uTexSize: ValueCell.create(0),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidReductionSchema };
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
|
||||
return HistopyramidReductionRenderable;
|
||||
ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, initialTexture);
|
||||
}
|
||||
return ctx.namedComputeRenderables[HistogramPyramidName];
|
||||
}
|
||||
|
||||
function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
|
||||
const values: Values<typeof HistopyramidReductionSchema> = {
|
||||
...QuadValues,
|
||||
tPreviousLevel: ValueCell.create(initialTexture),
|
||||
uSize: ValueCell.create(0),
|
||||
uTexSize: ValueCell.create(0),
|
||||
uFirst: ValueCell.create(true),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidReductionSchema };
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
|
||||
const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
|
||||
let textureFramebuffer = LevelTexturesFramebuffers[level];
|
||||
let textureFramebuffer = LevelTexturesFramebuffers[level];
|
||||
const size = Math.pow(2, level);
|
||||
if (textureFramebuffer === undefined) {
|
||||
const texture = ctx.resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
const framebuffer = ctx.resources.framebuffer();
|
||||
const texture = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
const framebuffer = getFramebuffer(`level${level}`, ctx);
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
textureFramebuffer = { texture, framebuffer };
|
||||
textureFramebuffer.texture.define(size, size);
|
||||
@@ -70,12 +79,28 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.disable(gl.CULL_FACE);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
state.colorMask(true, true, true, true);
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
function getFramebuffer(name: string, webgl: WebGLContext): Framebuffer {
|
||||
const _name = `${HistogramPyramidName}-${name}`;
|
||||
if (!webgl.namedFramebuffers[_name]) {
|
||||
webgl.namedFramebuffers[_name] = webgl.resources.framebuffer();
|
||||
}
|
||||
return webgl.namedFramebuffers[_name];
|
||||
}
|
||||
|
||||
function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
|
||||
const _name = `${HistogramPyramidName}-${name}`;
|
||||
if (!webgl.namedTextures[_name]) {
|
||||
webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
|
||||
}
|
||||
return webgl.namedTextures[_name];
|
||||
}
|
||||
|
||||
export interface HistogramPyramid {
|
||||
pyramidTex: Texture
|
||||
count: number
|
||||
@@ -84,24 +109,27 @@ export interface HistogramPyramid {
|
||||
scale: Vec2
|
||||
}
|
||||
|
||||
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
|
||||
const { gl, resources } = ctx;
|
||||
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2, gridTexDim: Vec3): HistogramPyramid {
|
||||
const { gl } = ctx;
|
||||
const w = inputTexture.getWidth();
|
||||
const h = inputTexture.getHeight();
|
||||
|
||||
// printTexture(ctx, inputTexture, 2)
|
||||
if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
|
||||
if (w !== h || !isPowerOfTwo(w)) {
|
||||
throw new Error('inputTexture must be of square power-of-two size');
|
||||
}
|
||||
|
||||
// This part set the levels
|
||||
const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2));
|
||||
const levels = Math.ceil(Math.log(w) / Math.log(2));
|
||||
const maxSize = Math.pow(2, levels);
|
||||
// console.log('levels', levels, 'maxSize', maxSize)
|
||||
// console.log('levels', levels, 'maxSize', maxSize, 'input', w);
|
||||
|
||||
const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
pyramidTexture.define(maxSize, maxSize);
|
||||
const pyramidTex = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
pyramidTex.define(maxSize, maxSize);
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
pyramidTexture.attachFramebuffer(framebuffer, 0);
|
||||
const framebuffer = getFramebuffer('pyramid', ctx);
|
||||
pyramidTex.attachFramebuffer(framebuffer, 0);
|
||||
gl.viewport(0, 0, maxSize, maxSize);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
const levelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
@@ -116,46 +144,42 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
const currLevel = levels - 1 - i;
|
||||
const tf = levelTexturesFramebuffers[currLevel];
|
||||
tf.framebuffer.bind();
|
||||
// levelTextures[currLevel].attachFramebuffer(framebuffer, 0)
|
||||
|
||||
const size = Math.pow(2, currLevel);
|
||||
// console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.viewport(0, 0, size, size);
|
||||
|
||||
ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
|
||||
ValueCell.update(renderable.values.uTexSize, size);
|
||||
ValueCell.updateIfChanged(renderable.values.uFirst, i === 0);
|
||||
if (i > 0) {
|
||||
ValueCell.update(renderable.values.tPreviousLevel, levelTexturesFramebuffers[levels - i].texture);
|
||||
renderable.update();
|
||||
}
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
gl.viewport(0, 0, size, size);
|
||||
gl.scissor(0, 0, size, size);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
|
||||
renderable.render();
|
||||
|
||||
pyramidTexture.bind(0);
|
||||
pyramidTex.bind(0);
|
||||
gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, offset, 0, 0, 0, size, size);
|
||||
pyramidTexture.unbind(0);
|
||||
pyramidTex.unbind(0);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
gl.finish();
|
||||
|
||||
// printTexture(ctx, pyramidTexture, 2)
|
||||
// printTexture(ctx, pyramidTex, 2)
|
||||
|
||||
//
|
||||
|
||||
const finalCount = getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture);
|
||||
const height = Math.ceil(finalCount / Math.pow(2, levels));
|
||||
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height)
|
||||
// console.log('height', height, 'finalCount', finalCount, 'scale', scale)
|
||||
// return at least a count of one to avoid issues downstram
|
||||
const count = Math.max(1, getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture));
|
||||
const height = Math.ceil(count / Math.pow(2, levels));
|
||||
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height);
|
||||
// console.log('height', height, 'finalCount', finalCount, 'scale', scale);
|
||||
|
||||
|
||||
return {
|
||||
pyramidTex: pyramidTexture,
|
||||
count: finalCount,
|
||||
height,
|
||||
levels,
|
||||
scale
|
||||
};
|
||||
return { pyramidTex, count, height, levels, scale };
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec } from '../../renderable/schema';
|
||||
@@ -21,33 +21,32 @@ const HistopyramidSumSchema = {
|
||||
tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
};
|
||||
|
||||
let HistopyramidSumRenderable: ComputeRenderable<Values<typeof HistopyramidSumSchema>>;
|
||||
const HistopyramidSumName = 'histopyramid-sum';
|
||||
|
||||
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
if (HistopyramidSumRenderable) {
|
||||
ValueCell.update(HistopyramidSumRenderable.values.tTexture, texture);
|
||||
HistopyramidSumRenderable.update();
|
||||
return HistopyramidSumRenderable;
|
||||
if (ctx.namedComputeRenderables[HistopyramidSumName]) {
|
||||
const v = ctx.namedComputeRenderables[HistopyramidSumName].values;
|
||||
|
||||
ValueCell.update(v.tTexture, texture);
|
||||
|
||||
ctx.namedComputeRenderables[HistopyramidSumName].update();
|
||||
} else {
|
||||
const values: Values<typeof HistopyramidSumSchema> = {
|
||||
...QuadValues,
|
||||
tTexture: ValueCell.create(texture),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidSumSchema };
|
||||
const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
HistopyramidSumRenderable = createComputeRenderable(renderItem, values);
|
||||
return HistopyramidSumRenderable;
|
||||
ctx.namedComputeRenderables[HistopyramidSumName] = createHistopyramidSumRenderable(ctx, texture);
|
||||
}
|
||||
return ctx.namedComputeRenderables[HistopyramidSumName];
|
||||
}
|
||||
|
||||
let SumTexture: Texture;
|
||||
function getSumTexture(ctx: WebGLContext) {
|
||||
if (SumTexture) return SumTexture;
|
||||
SumTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
SumTexture.define(1, 1);
|
||||
return SumTexture;
|
||||
function createHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
const values: Values<typeof HistopyramidSumSchema> = {
|
||||
...QuadValues,
|
||||
tTexture: ValueCell.create(texture),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidSumSchema };
|
||||
const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
function setRenderingDefaults(ctx: WebGLContext) {
|
||||
@@ -68,8 +67,16 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
|
||||
const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture);
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
const sumTexture = getSumTexture(ctx);
|
||||
if (!ctx.namedFramebuffers[HistopyramidSumName]) {
|
||||
ctx.namedFramebuffers[HistopyramidSumName] = resources.framebuffer();
|
||||
}
|
||||
const framebuffer = ctx.namedFramebuffers[HistopyramidSumName];
|
||||
|
||||
if (!ctx.namedTextures[HistopyramidSumName]) {
|
||||
ctx.namedTextures[HistopyramidSumName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
ctx.namedTextures[HistopyramidSumName].define(1, 1);
|
||||
}
|
||||
const sumTexture = ctx.namedTextures[HistopyramidSumName];
|
||||
sumTexture.attachFramebuffer(framebuffer, 0);
|
||||
|
||||
setRenderingDefaults(ctx);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -30,18 +30,36 @@ const ActiveVoxelsSchema = {
|
||||
uScale: UniformSpec('v2'),
|
||||
};
|
||||
|
||||
const ActiveVoxelsName = 'active-voxels';
|
||||
|
||||
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
|
||||
if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
|
||||
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values;
|
||||
|
||||
ValueCell.update(v.uQuadScale, scale);
|
||||
ValueCell.update(v.tVolumeData, volumeData);
|
||||
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
|
||||
ValueCell.update(v.uGridDim, gridDim);
|
||||
ValueCell.update(v.uGridTexDim, gridTexDim);
|
||||
ValueCell.update(v.uScale, scale);
|
||||
|
||||
ctx.namedComputeRenderables[ActiveVoxelsName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[ActiveVoxelsName] = createActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, scale);
|
||||
}
|
||||
return ctx.namedComputeRenderables[ActiveVoxelsName];
|
||||
}
|
||||
|
||||
function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
|
||||
const values: Values<typeof ActiveVoxelsSchema> = {
|
||||
...QuadValues,
|
||||
uQuadScale: ValueCell.create(scale),
|
||||
|
||||
tTriCount: ValueCell.create(getTriCount()),
|
||||
|
||||
uQuadScale: ValueCell.create(scale),
|
||||
tVolumeData: ValueCell.create(volumeData),
|
||||
uIsoValue: ValueCell.create(isoValue),
|
||||
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
|
||||
uScale: ValueCell.create(scale),
|
||||
};
|
||||
|
||||
@@ -57,7 +75,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.disable(gl.CULL_FACE);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
state.colorMask(true, true, true, true);
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
@@ -68,10 +86,16 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
|
||||
const width = volumeData.getWidth();
|
||||
const height = volumeData.getHeight();
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
if (!ctx.namedFramebuffers[ActiveVoxelsName]) {
|
||||
ctx.namedFramebuffers[ActiveVoxelsName] = resources.framebuffer();
|
||||
}
|
||||
const framebuffer = ctx.namedFramebuffers[ActiveVoxelsName];
|
||||
framebuffer.bind();
|
||||
|
||||
const activeVoxelsTex = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
if (!ctx.namedTextures[ActiveVoxelsName]) {
|
||||
ctx.namedTextures[ActiveVoxelsName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
const activeVoxelsTex = ctx.namedTextures[ActiveVoxelsName];
|
||||
activeVoxelsTex.define(width, height);
|
||||
|
||||
const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale);
|
||||
@@ -80,11 +104,14 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
|
||||
activeVoxelsTex.attachFramebuffer(framebuffer, 0);
|
||||
setRenderingDefaults(ctx);
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.scissor(0, 0, width, height);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
|
||||
renderable.render();
|
||||
|
||||
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim)
|
||||
// console.log('volumeData', volumeData)
|
||||
// console.log('at', readTexture(ctx, activeVoxelsTex))
|
||||
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim);
|
||||
// console.log('volumeData', volumeData);
|
||||
// console.log('at', readTexture(ctx, activeVoxelsTex));
|
||||
|
||||
gl.finish();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -38,18 +38,44 @@ const IsosurfaceSchema = {
|
||||
uScale: UniformSpec('v2'),
|
||||
};
|
||||
|
||||
const IsosurfaceName = 'isosurface';
|
||||
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values;
|
||||
|
||||
ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
|
||||
ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
|
||||
ValueCell.update(v.tVolumeData, volumeData);
|
||||
|
||||
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
|
||||
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
|
||||
ValueCell.updateIfChanged(v.uLevels, levels);
|
||||
ValueCell.updateIfChanged(v.uCount, count);
|
||||
|
||||
ValueCell.update(v.uGridDim, gridDim);
|
||||
ValueCell.update(v.uGridTexDim, gridTexDim);
|
||||
ValueCell.update(v.uGridTransform, transform);
|
||||
ValueCell.update(v.uScale, scale);
|
||||
|
||||
ctx.namedComputeRenderables[IsosurfaceName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
|
||||
}
|
||||
return ctx.namedComputeRenderables[IsosurfaceName];
|
||||
}
|
||||
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
|
||||
// console.log('uSize', Math.pow(2, levels))
|
||||
const values: Values<typeof IsosurfaceSchema> = {
|
||||
...QuadValues,
|
||||
uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
|
||||
|
||||
tTriIndices: ValueCell.create(getTriIndices()),
|
||||
|
||||
tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
|
||||
tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
|
||||
tVolumeData: ValueCell.create(volumeData),
|
||||
uIsoValue: ValueCell.create(isoValue),
|
||||
|
||||
uIsoValue: ValueCell.create(isoValue),
|
||||
uSize: ValueCell.create(Math.pow(2, levels)),
|
||||
uLevels: ValueCell.create(levels),
|
||||
uCount: ValueCell.create(count),
|
||||
@@ -57,7 +83,6 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
uGridTransform: ValueCell.create(transform),
|
||||
|
||||
uScale: ValueCell.create(scale),
|
||||
};
|
||||
|
||||
@@ -82,31 +107,25 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, vertexGroupTexture?: Texture, normalTexture?: Texture) {
|
||||
const { gl, resources } = ctx;
|
||||
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
|
||||
const width = pyramidTex.getWidth();
|
||||
|
||||
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
|
||||
// console.log('iso volumeData', volumeData)
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
|
||||
let needsClear = false;
|
||||
if (!ctx.namedFramebuffers[IsosurfaceName]) {
|
||||
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
|
||||
}
|
||||
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
|
||||
|
||||
if (!vertexGroupTexture) {
|
||||
vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
|
||||
} else if (vertexGroupTexture.getWidth() !== pyramidTex.getWidth() || vertexGroupTexture.getHeight() !== pyramidTex.getHeight()) {
|
||||
vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
|
||||
} else {
|
||||
needsClear = true;
|
||||
}
|
||||
vertexGroupTexture.define(width, height);
|
||||
|
||||
if (!normalTexture) {
|
||||
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
|
||||
} else if (normalTexture.getWidth() !== pyramidTex.getWidth() || normalTexture.getHeight() !== pyramidTex.getHeight()) {
|
||||
normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
|
||||
} else {
|
||||
needsClear = true;
|
||||
}
|
||||
normalTexture.define(width, height);
|
||||
|
||||
// const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// infoTex.define(pyramidTex.width, pyramidTex.height)
|
||||
@@ -147,11 +166,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
]);
|
||||
|
||||
setRenderingDefaults(ctx);
|
||||
gl.viewport(0, 0, pyramidTex.getWidth(), pyramidTex.getHeight());
|
||||
if (needsClear) gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
renderable.render();
|
||||
|
||||
gl.finish();
|
||||
gl.flush();
|
||||
|
||||
// const vgt = readTexture(ctx, vertexGroupTexture, pyramidTex.width, height)
|
||||
// console.log('vertexGroupTexture', vgt.array.subarray(0, 4 * count))
|
||||
|
||||
@@ -21,6 +21,7 @@ export type RenderableState = {
|
||||
colorOnly: boolean
|
||||
opaque: boolean
|
||||
writeDepth: boolean
|
||||
noClip: boolean
|
||||
}
|
||||
|
||||
export interface Renderable<T extends RenderableValues> {
|
||||
|
||||
@@ -29,6 +29,7 @@ export type ValueKind = keyof ValueKindType
|
||||
export type KindValue = UniformKindValue & DataTypeArrayType & TextureKindValue & ValueKindType
|
||||
|
||||
export type Values<S extends RenderableSchema> = { readonly [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> }
|
||||
export type UnboxedValues<S extends RenderableSchema> = { readonly [k in keyof S]: KindValue[S[k]['kind']] }
|
||||
|
||||
export function splitValues(schema: RenderableSchema, values: RenderableValues) {
|
||||
const attributeValues: AttributeValues = {};
|
||||
@@ -102,6 +103,7 @@ export type RenderableSchema = {
|
||||
}
|
||||
export type RenderableValues = { readonly [k: string]: ValueCell<any> }
|
||||
|
||||
|
||||
//
|
||||
|
||||
export const GlobalUniformSchema = {
|
||||
|
||||
@@ -22,6 +22,8 @@ export const TextureMeshSchema = {
|
||||
dFlatShaded: DefineSpec('boolean'),
|
||||
dDoubleSided: DefineSpec('boolean'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dGeoTexture: DefineSpec('boolean'),
|
||||
};
|
||||
export type TextureMeshSchema = typeof TextureMeshSchema
|
||||
|
||||
@@ -76,6 +76,7 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
|
||||
img.style.top = '0px';
|
||||
img.style.left = '0px';
|
||||
img.style.border = 'solid grey';
|
||||
img.style.pointerEvents = 'none';
|
||||
document.body.appendChild(img);
|
||||
}, 'image/png');
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { stringToWords } from '../mol-util/string';
|
||||
import { degToRad } from '../mol-math/misc';
|
||||
import { createNullTexture, Texture, Textures } from './webgl/texture';
|
||||
import { arrayMapUpsert } from '../mol-util/array';
|
||||
import { clamp } from '../mol-math/interpolate';
|
||||
|
||||
export interface RendererStats {
|
||||
programCount: number
|
||||
@@ -261,13 +262,20 @@ namespace Renderer {
|
||||
}
|
||||
|
||||
let definesNeedUpdate = false;
|
||||
if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
|
||||
ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
|
||||
definesNeedUpdate = true;
|
||||
}
|
||||
if (r.values.dClipVariant.ref.value !== clip.variant) {
|
||||
ValueCell.update(r.values.dClipVariant, clip.variant);
|
||||
definesNeedUpdate = true;
|
||||
if (r.state.noClip) {
|
||||
if (r.values.dClipObjectCount.ref.value !== 0) {
|
||||
ValueCell.update(r.values.dClipObjectCount, 0);
|
||||
definesNeedUpdate = true;
|
||||
}
|
||||
} else {
|
||||
if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
|
||||
ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
|
||||
definesNeedUpdate = true;
|
||||
}
|
||||
if (r.values.dClipVariant.ref.value !== clip.variant) {
|
||||
ValueCell.update(r.values.dClipVariant, clip.variant);
|
||||
definesNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
if (definesNeedUpdate) r.update();
|
||||
|
||||
@@ -474,8 +482,11 @@ namespace Renderer {
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
|
||||
// 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) {
|
||||
renderObject(r, 'colorWboit');
|
||||
}
|
||||
}
|
||||
@@ -487,8 +498,11 @@ namespace Renderer {
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle on renderable.state???
|
||||
if (r.values.uAlpha.ref.value < 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) {
|
||||
// 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) {
|
||||
renderObject(r, 'colorWboit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -51,10 +51,12 @@ import common_clip from './shader/chunks/common-clip.glsl';
|
||||
import common_frag_params from './shader/chunks/common-frag-params.glsl';
|
||||
import common_vert_params from './shader/chunks/common-vert-params.glsl';
|
||||
import common from './shader/chunks/common.glsl';
|
||||
import float_to_rgba from './shader/chunks/float-to-rgba.glsl';
|
||||
import light_frag_params from './shader/chunks/light-frag-params.glsl';
|
||||
import matrix_scale from './shader/chunks/matrix-scale.glsl';
|
||||
import normal_frag_params from './shader/chunks/normal-frag-params.glsl';
|
||||
import read_from_texture from './shader/chunks/read-from-texture.glsl';
|
||||
import rgba_to_float from './shader/chunks/rgba-to-float.glsl';
|
||||
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';
|
||||
@@ -83,10 +85,12 @@ const ShaderChunks: { [k: string]: string } = {
|
||||
common_frag_params,
|
||||
common_vert_params,
|
||||
common,
|
||||
float_to_rgba,
|
||||
light_frag_params,
|
||||
matrix_scale,
|
||||
normal_frag_params,
|
||||
read_from_texture,
|
||||
rgba_to_float,
|
||||
size_vert_params,
|
||||
texture3d_from_1d_trilinear,
|
||||
texture3d_from_2d_linear,
|
||||
|
||||
@@ -40,7 +40,18 @@ export default `
|
||||
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
|
||||
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
|
||||
);
|
||||
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
|
||||
int ci = int(intMod(coord.x, 4.0));
|
||||
int ri = int(intMod(coord.y, 4.0));
|
||||
#if __VERSION__ == 100
|
||||
vec4 i = vec4(float(ci * 4 + ri));
|
||||
vec4 v = thresholdMatrix[0] * vec4(equal(i, vec4(0.0, 1.0, 2.0, 3.0))) +
|
||||
thresholdMatrix[1] * vec4(equal(i, vec4(4.0, 5.0, 6.0, 7.0))) +
|
||||
thresholdMatrix[2] * vec4(equal(i, vec4(8.0, 9.0, 10.0, 11.0))) +
|
||||
thresholdMatrix[3] * vec4(equal(i, vec4(12.0, 13.0, 14.0, 15.0)));
|
||||
at = v.x + v.y + v.z + v.w;
|
||||
#else
|
||||
at = thresholdMatrix[ci][ri];
|
||||
#endif
|
||||
|
||||
if (ta < 0.99 && (ta < 0.01 || ta < at)) {
|
||||
discard;
|
||||
|
||||
46
src/mol-gl/shader/chunks/float-to-rgba.glsl.ts
Normal file
46
src/mol-gl/shader/chunks/float-to-rgba.glsl.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default `
|
||||
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
|
||||
// MIT License, Copyright (c) 2020 Equinor
|
||||
|
||||
float shiftRight (float v, float amt) {
|
||||
v = floor(v) + 0.5;
|
||||
return floor(v / exp2(amt));
|
||||
}
|
||||
float shiftLeft (float v, float amt) {
|
||||
return floor(v * exp2(amt) + 0.5);
|
||||
}
|
||||
float maskLast (float v, float bits) {
|
||||
return mod(v, shiftLeft(1.0, bits));
|
||||
}
|
||||
float extractBits (float num, float from, float to) {
|
||||
from = floor(from + 0.5); to = floor(to + 0.5);
|
||||
return maskLast(shiftRight(num, from), to - from);
|
||||
}
|
||||
|
||||
vec4 floatToRgba(float texelFloat, bool littleEndian) {
|
||||
if (texelFloat == 0.0) return vec4(0.0, 0.0, 0.0, 0.0);
|
||||
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
|
||||
texelFloat = abs(texelFloat);
|
||||
float exponent = floor(log2(texelFloat));
|
||||
float biased_exponent = exponent + 127.0;
|
||||
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
|
||||
float t = biased_exponent / 2.0;
|
||||
float last_bit_of_biased_exponent = fract(t) * 2.0;
|
||||
float remaining_bits_of_biased_exponent = floor(t);
|
||||
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
|
||||
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
|
||||
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
|
||||
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
|
||||
return (
|
||||
littleEndian
|
||||
? vec4(byte4, byte3, byte2, byte1)
|
||||
: vec4(byte1, byte2, byte3, byte4)
|
||||
);
|
||||
}
|
||||
`;
|
||||
91
src/mol-gl/shader/chunks/rgba-to-float.glsl.ts
Normal file
91
src/mol-gl/shader/chunks/rgba-to-float.glsl.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default `
|
||||
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
// OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
ivec4 floatsToBytes(vec4 inputFloats, bool littleEndian) {
|
||||
ivec4 bytes = ivec4(inputFloats * 255.0);
|
||||
return (
|
||||
littleEndian
|
||||
? bytes.abgr
|
||||
: bytes
|
||||
);
|
||||
}
|
||||
|
||||
// Break the four bytes down into an array of 32 bits.
|
||||
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
|
||||
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
|
||||
float acc = float(bytes[channelIndex]);
|
||||
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
|
||||
float powerOfTwo = exp2(float(indexInByte));
|
||||
bool bit = acc >= powerOfTwo;
|
||||
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
|
||||
acc = mod(acc, powerOfTwo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the exponent of the 32-bit float.
|
||||
float getExponent(bool bits[32]) {
|
||||
const int startIndex = 1;
|
||||
const int bitStringLength = 8;
|
||||
const int endBeforeIndex = startIndex + bitStringLength;
|
||||
float acc = 0.0;
|
||||
int pow2 = bitStringLength - 1;
|
||||
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
|
||||
acc += float(bits[bitIndex]) * exp2(float(pow2--));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Compute the mantissa of the 32-bit float.
|
||||
float getMantissa(bool bits[32], bool subnormal) {
|
||||
const int startIndex = 9;
|
||||
const int bitStringLength = 23;
|
||||
const int endBeforeIndex = startIndex + bitStringLength;
|
||||
// Leading/implicit/hidden bit convention:
|
||||
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
|
||||
float acc = float(!subnormal) * exp2(float(bitStringLength));
|
||||
int pow2 = bitStringLength - 1;
|
||||
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
|
||||
acc += float(bits[bitIndex]) * exp2(float(pow2--));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Parse the float from its 32 bits.
|
||||
float bitsToFloat(bool bits[32]) {
|
||||
float signBit = float(bits[0]) * -2.0 + 1.0;
|
||||
float exponent = getExponent(bits);
|
||||
bool subnormal = abs(exponent - 0.0) < 0.01;
|
||||
float mantissa = getMantissa(bits, subnormal);
|
||||
float exponentBias = 127.0;
|
||||
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
|
||||
}
|
||||
|
||||
float rgbaToFloat(vec4 texelRGBA, bool littleEndian) {
|
||||
ivec4 rgbaBytes = floatsToBytes(texelRGBA, littleEndian);
|
||||
bool bits[32];
|
||||
bytesToBits(rgbaBytes, bits);
|
||||
return bitsToFloat(bits);
|
||||
}
|
||||
`;
|
||||
@@ -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>
|
||||
* @author Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
@@ -31,7 +31,7 @@ uniform float uCurrentX;
|
||||
uniform float uCurrentY;
|
||||
uniform float uAlpha;
|
||||
uniform float uResolution;
|
||||
uniform float uRadiusFactor;
|
||||
uniform float uRadiusFactorInv;
|
||||
|
||||
void main() {
|
||||
vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
|
||||
@@ -40,16 +40,16 @@ void main() {
|
||||
|
||||
#if defined(dCalcType_density)
|
||||
float density = exp(-uAlpha * ((dist * dist) * vRadiusSqInv));
|
||||
gl_FragColor.a = density / uRadiusFactor;
|
||||
gl_FragColor.a = density * uRadiusFactorInv;
|
||||
#elif defined(dCalcType_minDistance)
|
||||
gl_FragColor.a = 1.0 - dist / uRadiusFactor;
|
||||
gl_FragColor.a = 1.0 - dist * uRadiusFactorInv;
|
||||
#elif defined(dCalcType_groupId)
|
||||
#if defined(dGridTexType_2d)
|
||||
float minDistance = 1.0 - texture2D(tMinDistanceTex, (gl_FragCoord.xy) / (uGridTexDim.xy / uGridTexScale)).a;
|
||||
#elif defined(dGridTexType_3d)
|
||||
float minDistance = 1.0 - texelFetch(tMinDistanceTex, ivec3(gl_FragCoord.xy, uCurrentSlice), 0).a;
|
||||
#endif
|
||||
if (dist / uRadiusFactor > minDistance + uResolution * 0.05)
|
||||
if (dist * uRadiusFactorInv > minDistance + uResolution * 0.05)
|
||||
discard;
|
||||
gl_FragColor.rgb = encodeFloatRGB(vGroup);
|
||||
#endif
|
||||
|
||||
@@ -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 Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
@@ -21,7 +21,6 @@ varying float vRadiusSqInv;
|
||||
|
||||
uniform vec3 uBboxSize;
|
||||
uniform vec3 uBboxMin;
|
||||
uniform float uCurrentSlice;
|
||||
uniform float uResolution;
|
||||
|
||||
void main() {
|
||||
@@ -29,7 +28,7 @@ void main() {
|
||||
#if defined(dCalcType_groupId)
|
||||
vGroup = aGroup;
|
||||
#endif
|
||||
gl_PointSize = floor(((aRadius * 6.0) / uResolution) + 0.5);
|
||||
gl_PointSize = ceil(((aRadius * 3.0) / uResolution) + uResolution);
|
||||
vPosition = (aPosition - uBboxMin) / uResolution;
|
||||
gl_Position = vec4(((aPosition - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,28 @@ precision highp sampler2D;
|
||||
// input texture (previous level used to evaluate the new level)
|
||||
uniform sampler2D tPreviousLevel;
|
||||
|
||||
// 1/size of the previous level texture.
|
||||
// inverted size of the previous level texture.
|
||||
uniform float uSize;
|
||||
uniform float uTexSize;
|
||||
uniform bool uFirst;
|
||||
|
||||
void main(void) {
|
||||
float k = 0.5 * uSize;
|
||||
vec2 position = floor((gl_FragCoord.xy / uTexSize) / uSize) * uSize;
|
||||
float a = texture2D(tPreviousLevel, position).r;
|
||||
float b = texture2D(tPreviousLevel, position + vec2(k, 0.)).r;
|
||||
float c = texture2D(tPreviousLevel, position + vec2(0., k)).r;
|
||||
float d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
|
||||
float a, b, c, d;
|
||||
|
||||
if (uFirst) {
|
||||
a = texture2D(tPreviousLevel, position).r * 255.0;
|
||||
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r * 255.0;
|
||||
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r * 255.0;
|
||||
d = texture2D(tPreviousLevel, position + vec2(k, k)).r * 255.0;
|
||||
} else {
|
||||
a = texture2D(tPreviousLevel, position).r;
|
||||
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r;
|
||||
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r;
|
||||
d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
|
||||
}
|
||||
|
||||
gl_FragColor.a = a;
|
||||
gl_FragColor.b = a + b;
|
||||
gl_FragColor.g = gl_FragColor.b + c;
|
||||
|
||||
@@ -11,7 +11,7 @@ uniform vec3 uGridTexDim;
|
||||
uniform vec2 uScale;
|
||||
|
||||
// cube corners
|
||||
const vec3 c0 = vec3(0., 0., 0.);
|
||||
// const vec3 c0 = vec3(0., 0., 0.);
|
||||
const vec3 c1 = vec3(1., 0., 0.);
|
||||
const vec3 c2 = vec3(1., 1., 0.);
|
||||
const vec3 c3 = vec3(0., 1., 0.);
|
||||
@@ -25,8 +25,7 @@ vec3 index3dFrom2d(vec2 coord) {
|
||||
vec2 columnRow = floor(gridTexPos / uGridDim.xy);
|
||||
vec2 posXY = gridTexPos - columnRow * uGridDim.xy;
|
||||
float posZ = columnRow.y * floor(uGridTexDim.x / uGridDim.x) + columnRow.x;
|
||||
vec3 posXYZ = vec3(posXY, posZ) / uGridDim;
|
||||
return posXYZ;
|
||||
return vec3(posXY, posZ);
|
||||
}
|
||||
|
||||
float intDiv(float a, float b) { return float(int(a) / int(b)); }
|
||||
@@ -37,12 +36,11 @@ vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim)
|
||||
float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x;
|
||||
float row = floor(intDiv(zSlice * gridDim.x, texDim.x));
|
||||
vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / (texDim / uScale);
|
||||
// return texture2D(tex, coord + 0.5 / texDim);
|
||||
return texture2D(tex, coord);
|
||||
}
|
||||
|
||||
vec4 voxel(vec3 pos) {
|
||||
return texture3dFrom2dNearest(tVolumeData, pos, uGridDim, uGridTexDim.xy);
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
@@ -51,20 +49,26 @@ void main(void) {
|
||||
|
||||
// get MC case as the sum of corners that are below the given iso level
|
||||
float c = step(voxel(posXYZ).a, uIsoValue)
|
||||
+ 2. * step(voxel(posXYZ + c1 / uGridDim).a, uIsoValue)
|
||||
+ 4. * step(voxel(posXYZ + c2 / uGridDim).a, uIsoValue)
|
||||
+ 8. * step(voxel(posXYZ + c3 / uGridDim).a, uIsoValue)
|
||||
+ 16. * step(voxel(posXYZ + c4 / uGridDim).a, uIsoValue)
|
||||
+ 32. * step(voxel(posXYZ + c5 / uGridDim).a, uIsoValue)
|
||||
+ 64. * step(voxel(posXYZ + c6 / uGridDim).a, uIsoValue)
|
||||
+ 128. * step(voxel(posXYZ + c7 / uGridDim).a, uIsoValue);
|
||||
+ 2. * step(voxel(posXYZ + c1).a, uIsoValue)
|
||||
+ 4. * step(voxel(posXYZ + c2).a, uIsoValue)
|
||||
+ 8. * step(voxel(posXYZ + c3).a, uIsoValue)
|
||||
+ 16. * step(voxel(posXYZ + c4).a, uIsoValue)
|
||||
+ 32. * step(voxel(posXYZ + c5).a, uIsoValue)
|
||||
+ 64. * step(voxel(posXYZ + c6).a, uIsoValue)
|
||||
+ 128. * step(voxel(posXYZ + c7).a, uIsoValue);
|
||||
c *= step(c, 254.);
|
||||
|
||||
// handle out of bounds positions
|
||||
posXYZ += 1.0;
|
||||
posXYZ.xy += 1.0; // one pixel padding (usually ok even if the texture has no padding)
|
||||
if (posXYZ.x >= uGridDim.x || posXYZ.y >= uGridDim.y || posXYZ.z >= uGridDim.z)
|
||||
c = 0.0;
|
||||
|
||||
// get total triangles to generate for calculated MC case from triCount texture
|
||||
float totalTrianglesToGenerate = texture2D(tTriCount, vec2(intMod(c, 16.), floor(c / 16.)) / 16.).a;
|
||||
gl_FragColor = vec4(vec3(floor(totalTrianglesToGenerate * 255.0 + 0.5) * 3.0), c);
|
||||
gl_FragColor = vec4(vec3(totalTrianglesToGenerate * 3.0), c / 255.0);
|
||||
|
||||
// gl_FragColor = vec4(255.0, 0.0, 0.0, voxel(posXYZ + c4 / uGridDim).a * 255.0);
|
||||
// gl_FragColor = vec4(255.0, 0.0, 0.0, voxel(posXYZ + c4).a * 255.0);
|
||||
// gl_FragColor = vec4(255.0, 0.0, 0.0, voxel(posXYZ).a * 255.0);
|
||||
|
||||
// vec2 uv = vCoordinate;
|
||||
|
||||
@@ -19,8 +19,6 @@ uniform mat4 uGridTransform;
|
||||
// scale to volume data coord
|
||||
uniform vec2 uScale;
|
||||
|
||||
// varying vec2 vCoordinate;
|
||||
|
||||
#include common
|
||||
|
||||
// cube corners
|
||||
@@ -33,15 +31,12 @@ const vec3 c5 = vec3(1., 0., 1.);
|
||||
const vec3 c6 = vec3(1., 1., 1.);
|
||||
const vec3 c7 = vec3(0., 1., 1.);
|
||||
|
||||
const float EPS = 0.00001;
|
||||
|
||||
vec3 index3dFrom2d(vec2 coord) {
|
||||
vec2 gridTexPos = coord * uGridTexDim.xy;
|
||||
vec2 columnRow = floor(gridTexPos / uGridDim.xy);
|
||||
vec2 posXY = gridTexPos - columnRow * uGridDim.xy;
|
||||
float posZ = columnRow.y * floor(uGridTexDim.x / uGridDim.x) + columnRow.x;
|
||||
vec3 posXYZ = vec3(posXY, posZ);
|
||||
return posXYZ;
|
||||
return vec3(posXY, posZ);
|
||||
}
|
||||
|
||||
vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
|
||||
@@ -50,11 +45,10 @@ vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim)
|
||||
float row = floor(intDiv(zSlice * gridDim.x, texDim.x));
|
||||
vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / (texDim / uScale);
|
||||
return texture2D(tex, coord + 0.5 / (texDim / uScale));
|
||||
// return texture2D(tex, coord);
|
||||
}
|
||||
|
||||
vec4 voxel(vec3 pos) {
|
||||
return texture3dFrom2dNearest(tVolumeData, pos, uGridDim, uGridTexDim.xy);
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
@@ -78,7 +72,7 @@ void main(void) {
|
||||
vec4 vI4 = vec4(vI);
|
||||
|
||||
// traverse the different levels of the pyramid
|
||||
for(int i = 1; i < 12; i++) {
|
||||
for(int i = 1; i < 14; i++) {
|
||||
if(float(i) >= uLevels) break;
|
||||
|
||||
offset -= diff;
|
||||
@@ -103,7 +97,7 @@ void main(void) {
|
||||
vec2 coord2d = position / uScale;
|
||||
vec3 coord3d = floor(index3dFrom2d(coord2d) + 0.5);
|
||||
|
||||
float edgeIndex = floor(texture2D(tActiveVoxelsBase, position).a + 0.5);
|
||||
float edgeIndex = floor(texture2D(tActiveVoxelsBase, position).a * 255.0 + 0.5);
|
||||
|
||||
// current vertex for the up to 15 MC cases
|
||||
float currentVertex = vI - dot(m, starts);
|
||||
@@ -176,27 +170,34 @@ void main(void) {
|
||||
// b0 = floor(b0 + 0.5);
|
||||
// b1 = floor(b1 + 0.5);
|
||||
|
||||
vec4 d0 = voxel(b0 / uGridDim);
|
||||
vec4 d1 = voxel(b1 / uGridDim);
|
||||
vec4 d0 = voxel(b0);
|
||||
vec4 d1 = voxel(b1);
|
||||
|
||||
float v0 = d0.a;
|
||||
float v1 = d1.a;
|
||||
|
||||
float t = (uIsoValue - v0) / (v0 - v1);
|
||||
// t = -0.5;
|
||||
gl_FragData[0].xyz = (uGridTransform * vec4(b0 + t * (b0 - b1), 1.0)).xyz;
|
||||
gl_FragData[0].w = decodeFloatRGB(d0.rgb); // group id
|
||||
|
||||
// group id
|
||||
#if __VERSION__ == 100
|
||||
// webgl1 does not support 'flat' interpolation (i.e. no interpolation)
|
||||
// so we ensure a constant group id per triangle
|
||||
gl_FragData[0].w = decodeFloatRGB(voxel(coord3d).rgb);
|
||||
#else
|
||||
gl_FragData[0].w = t < 0.5 ? decodeFloatRGB(d0.rgb) : decodeFloatRGB(d1.rgb);
|
||||
#endif
|
||||
|
||||
// normals from gradients
|
||||
vec3 n0 = -normalize(vec3(
|
||||
voxel((b0 - c1) / uGridDim).a - voxel((b0 + c1) / uGridDim).a,
|
||||
voxel((b0 - c3) / uGridDim).a - voxel((b0 + c3) / uGridDim).a,
|
||||
voxel((b0 - c4) / uGridDim).a - voxel((b0 + c4) / uGridDim).a
|
||||
voxel(b0 - c1).a - voxel(b0 + c1).a,
|
||||
voxel(b0 - c3).a - voxel(b0 + c3).a,
|
||||
voxel(b0 - c4).a - voxel(b0 + c4).a
|
||||
));
|
||||
vec3 n1 = -normalize(vec3(
|
||||
voxel((b1 - c1) / uGridDim).a - voxel((b1 + c1) / uGridDim).a,
|
||||
voxel((b1 - c3) / uGridDim).a - voxel((b1 + c3) / uGridDim).a,
|
||||
voxel((b1 - c4) / uGridDim).a - voxel((b1 + c4) / uGridDim).a
|
||||
voxel(b1 - c1).a - voxel(b1 + c1).a,
|
||||
voxel(b1 - c3).a - voxel(b1 + c3).a,
|
||||
voxel(b1 - c4).a - voxel(b1 + c4).a
|
||||
));
|
||||
gl_FragData[1].xyz = -vec3(
|
||||
n0.x + t * (n0.x - n1.x),
|
||||
|
||||
53
src/mol-gl/shader/util/grid3d-template.frag.ts
Normal file
53
src/mol-gl/shader/util/grid3d-template.frag.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform vec2 uQuadShift;
|
||||
uniform vec3 uDimensions;
|
||||
uniform vec3 uMin;
|
||||
uniform vec3 uDelta;
|
||||
uniform bool uLittleEndian;
|
||||
uniform float uWidth;
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
uniform sampler2D tCumulativeSum;
|
||||
#endif
|
||||
|
||||
{UNIFORMS}
|
||||
|
||||
{UTILS}
|
||||
|
||||
#include float_to_rgba
|
||||
#ifdef CUMULATIVE
|
||||
#include rgba_to_float
|
||||
#endif
|
||||
|
||||
float intDiv(float a, float b) { return float(int(a) / int(b)); }
|
||||
float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
|
||||
|
||||
void main(void) {
|
||||
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
|
||||
|
||||
// axis order fast to slow Z, Y, X
|
||||
// TODO: support arbitrary axis orders?
|
||||
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
|
||||
float j = intMod(kk, uDimensions.y);
|
||||
float i = intDiv(kk, uDimensions.y);
|
||||
|
||||
vec3 xyz = uMin + uDelta * vec3(i, j, k);
|
||||
|
||||
{MAIN}
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)), uLittleEndian);
|
||||
#endif
|
||||
gl_FragColor = floatToRgba({RETURN}, uLittleEndian);
|
||||
}
|
||||
`;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { getErrorDescription, getGLContext } from './context';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
import { getErrorDescription } from './context';
|
||||
import { getProgram } from './program';
|
||||
import { getShader } from './shader';
|
||||
|
||||
@@ -111,6 +111,27 @@ export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_fl
|
||||
return gl.getExtension('OES_texture_float_linear');
|
||||
}
|
||||
|
||||
export interface COMPAT_texture_half_float {
|
||||
readonly HALF_FLOAT: number
|
||||
}
|
||||
|
||||
export function getTextureHalfFloat(gl: GLRenderingContext): COMPAT_texture_half_float | null {
|
||||
if (isWebGL2(gl)) {
|
||||
return { HALF_FLOAT: gl.HALF_FLOAT };
|
||||
} else {
|
||||
const ext = gl.getExtension('OES_texture_half_float');
|
||||
if (ext === null) return null;
|
||||
return { HALF_FLOAT: ext.HALF_FLOAT_OES };
|
||||
}
|
||||
}
|
||||
|
||||
export interface COMPAT_texture_half_float_linear {
|
||||
}
|
||||
|
||||
export function getTextureHalfFloatLinear(gl: GLRenderingContext): COMPAT_texture_half_float_linear | null {
|
||||
return gl.getExtension('OES_texture_half_float_linear');
|
||||
}
|
||||
|
||||
export interface COMPAT_blend_minmax {
|
||||
readonly MIN: number
|
||||
readonly MAX: number
|
||||
@@ -146,13 +167,35 @@ export function getColorBufferFloat(gl: GLRenderingContext): COMPAT_color_buffer
|
||||
const ext = gl.getExtension('WEBGL_color_buffer_float');
|
||||
if (ext === null) {
|
||||
// test as support may not be advertised by browsers
|
||||
return testColorBufferFloat() ? { RGBA32F: 0x8814 } : null;
|
||||
gl.getExtension('OES_texture_float');
|
||||
return testColorBuffer(gl, gl.FLOAT) ? { RGBA32F: 0x8814 } : null;
|
||||
}
|
||||
gl.getExtension('EXT_float_blend');
|
||||
return { RGBA32F: ext.RGBA32F_EXT };
|
||||
}
|
||||
}
|
||||
|
||||
export interface COMPAT_color_buffer_half_float {
|
||||
readonly RGBA16F: number;
|
||||
}
|
||||
|
||||
export function getColorBufferHalfFloat(gl: GLRenderingContext): COMPAT_color_buffer_half_float | null {
|
||||
if (isWebGL2(gl)) {
|
||||
if (gl.getExtension('EXT_color_buffer_half_float') === null) return null;
|
||||
gl.getExtension('EXT_float_blend');
|
||||
return { RGBA16F: gl.RGBA16F };
|
||||
} else {
|
||||
const ext = gl.getExtension('EXT_color_buffer_half_float');
|
||||
if (ext === null) {
|
||||
// test as support may not be advertised by browsers
|
||||
gl.getExtension('OES_texture_half_float');
|
||||
return testColorBuffer(gl, 0x8D61) ? { RGBA16F: 0x881A } : null;
|
||||
}
|
||||
gl.getExtension('EXT_float_blend');
|
||||
return { RGBA16F: ext.RGBA16F_EXT };
|
||||
}
|
||||
}
|
||||
|
||||
export interface COMPAT_draw_buffers {
|
||||
drawBuffers(buffers: number[]): void;
|
||||
readonly COLOR_ATTACHMENT0: number;
|
||||
@@ -283,7 +326,7 @@ const TextureTestVertShader = `
|
||||
attribute vec4 aPosition;
|
||||
|
||||
void main() {
|
||||
gl_Position = aPosition;
|
||||
gl_Position = aPosition;
|
||||
}`;
|
||||
|
||||
const TextureTestFragShader = `
|
||||
@@ -292,28 +335,15 @@ uniform vec4 uColor;
|
||||
uniform sampler2D uTexture;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(uTexture, vec2(0.5, 0.5)) * uColor;
|
||||
gl_FragColor = texture2D(uTexture, vec2(0.5, 0.5)) * uColor;
|
||||
}`;
|
||||
|
||||
const TextureTestTexCoords = new Float32Array([
|
||||
-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0
|
||||
]);
|
||||
|
||||
export function testColorBufferFloat() {
|
||||
// adapted from https://stackoverflow.com/questions/28827511/
|
||||
|
||||
// Get A WebGL context
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 16;
|
||||
canvas.height = 16;
|
||||
canvas.style.width = `${16}px`;
|
||||
canvas.style.height = `${16}px`;
|
||||
const gl = getGLContext(canvas);
|
||||
if (gl === null) throw new Error('Unable to get WebGL context');
|
||||
|
||||
const type = gl.FLOAT;
|
||||
gl.getExtension('OES_texture_float');
|
||||
|
||||
// adapted from https://stackoverflow.com/questions/28827511/
|
||||
export function testColorBuffer(gl: GLRenderingContext, type: number) {
|
||||
// setup shaders
|
||||
const vertShader = getShader(gl, { type: 'vert', source: TextureTestVertShader });
|
||||
const fragShader = getShader(gl, { type: 'frag', source: TextureTestFragShader });
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -193,7 +193,7 @@ export interface WebGLContext {
|
||||
readonly isContextLost: boolean
|
||||
readonly contextRestored: BehaviorSubject<now.Timestamp>
|
||||
setContextLost: () => void
|
||||
handleContextRestored: () => void
|
||||
handleContextRestored: (extraResets?: () => void) => void
|
||||
|
||||
/** Cache for compute renderables, managed by consumers */
|
||||
readonly namedComputeRenderables: { [name: string]: ComputeRenderable<any> }
|
||||
@@ -202,7 +202,7 @@ export interface WebGLContext {
|
||||
/** Cache for textures, managed by consumers */
|
||||
readonly namedTextures: { [name: string]: Texture }
|
||||
|
||||
createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => RenderTarget
|
||||
createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32' | 'fp16', filter?: TextureFilter) => RenderTarget
|
||||
unbindFramebuffer: () => void
|
||||
readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => void
|
||||
readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
|
||||
@@ -210,7 +210,7 @@ export interface WebGLContext {
|
||||
waitForGpuCommandsCompleteSync: () => void
|
||||
getDrawingBufferPixelData: () => PixelData
|
||||
clear: (red: number, green: number, blue: number, alpha: number) => void
|
||||
destroy: () => void
|
||||
destroy: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
|
||||
}
|
||||
|
||||
export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScale: number }> = {}): WebGLContext {
|
||||
@@ -232,7 +232,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
}
|
||||
|
||||
let isContextLost = false;
|
||||
let contextRestored = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
const contextRestored = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
|
||||
let readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>;
|
||||
if (isWebGL2(gl)) {
|
||||
@@ -302,7 +302,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
setContextLost: () => {
|
||||
isContextLost = true;
|
||||
},
|
||||
handleContextRestored: () => {
|
||||
handleContextRestored: (extraResets?: () => void) => {
|
||||
Object.assign(extensions, createExtensions(gl));
|
||||
|
||||
state.reset();
|
||||
@@ -312,12 +312,13 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
|
||||
resources.reset();
|
||||
renderTargets.forEach(rt => rt.reset());
|
||||
extraResets?.();
|
||||
|
||||
isContextLost = false;
|
||||
contextRestored.next(now());
|
||||
},
|
||||
|
||||
createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => {
|
||||
createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32' | 'fp16', filter?: TextureFilter) => {
|
||||
const renderTarget = createRenderTarget(gl, resources, width, height, depth, type, filter);
|
||||
renderTargets.add(renderTarget);
|
||||
return {
|
||||
@@ -347,9 +348,12 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
},
|
||||
|
||||
destroy: () => {
|
||||
destroy: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
|
||||
resources.destroy();
|
||||
unbindResources(gl);
|
||||
|
||||
// to aid GC
|
||||
if (!options?.doNotForceWebGLContextLoss) gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB } from './compat';
|
||||
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat } from './compat';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
|
||||
export type WebGLExtensions = {
|
||||
@@ -14,11 +14,14 @@ export type WebGLExtensions = {
|
||||
standardDerivatives: COMPAT_standard_derivatives | null
|
||||
textureFloat: COMPAT_texture_float | null
|
||||
textureFloatLinear: COMPAT_texture_float_linear | null
|
||||
textureHalfFloat: COMPAT_texture_half_float | null
|
||||
textureHalfFloatLinear: COMPAT_texture_half_float_linear | null
|
||||
depthTexture: COMPAT_depth_texture | null
|
||||
blendMinMax: COMPAT_blend_minmax | null
|
||||
vertexArrayObject: COMPAT_vertex_array_object | null
|
||||
fragDepth: COMPAT_frag_depth | null
|
||||
colorBufferFloat: COMPAT_color_buffer_float | null
|
||||
colorBufferHalfFloat: COMPAT_color_buffer_half_float | null
|
||||
drawBuffers: COMPAT_draw_buffers | null
|
||||
shaderTextureLod: COMPAT_shader_texture_lod | null
|
||||
sRGB: COMPAT_sRGB | null
|
||||
@@ -50,6 +53,16 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
|
||||
// - can't be a required extension because it is not supported by `headless-gl`
|
||||
console.log('Could not find support for "texture_float_linear"');
|
||||
}
|
||||
const textureHalfFloat = getTextureHalfFloat(gl);
|
||||
if (isDebugMode && textureHalfFloat === null) {
|
||||
console.log('Could not find support for "texture_half_float"');
|
||||
}
|
||||
const textureHalfFloatLinear = getTextureHalfFloatLinear(gl);
|
||||
if (isDebugMode && textureHalfFloatLinear === null) {
|
||||
// TODO handle non-support downstream (no gpu gaussian calc, no gpu mc???)
|
||||
// - can't be a required extension because it is not supported by `headless-gl`
|
||||
console.log('Could not find support for "texture_half_float_linear"');
|
||||
}
|
||||
const depthTexture = getDepthTexture(gl);
|
||||
if (isDebugMode && depthTexture === null) {
|
||||
console.log('Could not find support for "depth_texture"');
|
||||
@@ -72,6 +85,10 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
|
||||
if (isDebugMode && colorBufferFloat === null) {
|
||||
console.log('Could not find support for "color_buffer_float"');
|
||||
}
|
||||
const colorBufferHalfFloat = getColorBufferHalfFloat(gl);
|
||||
if (isDebugMode && colorBufferHalfFloat === null) {
|
||||
console.log('Could not find support for "color_buffer_half_float"');
|
||||
}
|
||||
const drawBuffers = getDrawBuffers(gl);
|
||||
if (isDebugMode && drawBuffers === null) {
|
||||
console.log('Could not find support for "draw_buffers"');
|
||||
@@ -90,6 +107,8 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
|
||||
standardDerivatives,
|
||||
textureFloat,
|
||||
textureFloatLinear,
|
||||
textureHalfFloat,
|
||||
textureHalfFloatLinear,
|
||||
elementIndexUint,
|
||||
depthTexture,
|
||||
|
||||
@@ -97,6 +116,7 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
|
||||
vertexArrayObject,
|
||||
fragDepth,
|
||||
colorBufferFloat,
|
||||
colorBufferHalfFloat,
|
||||
drawBuffers,
|
||||
shaderTextureLod,
|
||||
sRGB,
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface Program {
|
||||
use: () => void
|
||||
setUniforms: (uniformValues: UniformsList) => void
|
||||
bindAttributes: (attribueBuffers: AttributeBuffers) => void
|
||||
bindTextures: (textures: Textures, startingTargetUnit?: number) => void
|
||||
bindTextures: (textures: Textures, startingTargetUnit: number) => void
|
||||
|
||||
reset: () => void
|
||||
destroy: () => void
|
||||
@@ -198,9 +198,7 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
if (l !== -1) buffer.bind(l);
|
||||
}
|
||||
},
|
||||
bindTextures: (textures: Textures, startingTargetUnit?: number) => {
|
||||
startingTargetUnit = startingTargetUnit ?? 0;
|
||||
|
||||
bindTextures: (textures: Textures, startingTargetUnit: number) => {
|
||||
for (let i = 0, il = textures.length; i < il; ++i) {
|
||||
const [k, texture] = textures[i];
|
||||
const l = locations[k];
|
||||
|
||||
@@ -173,7 +173,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
program.bindTextures(sharedTexturesList, 0);
|
||||
program.bindTextures(textures, sharedTexturesList.length);
|
||||
} else {
|
||||
program.bindTextures(textures);
|
||||
program.bindTextures(textures, 0);
|
||||
}
|
||||
} else {
|
||||
const vertexArray = vertexArrays[variant];
|
||||
@@ -191,7 +191,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
program.bindTextures(sharedTexturesList, 0);
|
||||
program.bindTextures(textures, sharedTexturesList.length);
|
||||
} else {
|
||||
program.bindTextures(textures);
|
||||
program.bindTextures(textures, 0);
|
||||
}
|
||||
if (vertexArray) {
|
||||
vertexArray.bind();
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -26,12 +26,14 @@ export interface RenderTarget {
|
||||
destroy: () => void
|
||||
}
|
||||
|
||||
export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number, depth = true, type: 'uint8' | 'float32' = 'uint8', filter: TextureFilter = 'nearest'): RenderTarget {
|
||||
export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number, depth = true, type: 'uint8' | 'float32' | 'fp16' = 'uint8', filter: TextureFilter = 'nearest'): RenderTarget {
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
const targetTexture = type === 'float32'
|
||||
? resources.texture('image-float32', 'rgba', 'float', filter)
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', filter);
|
||||
const targetTexture = type === 'fp16'
|
||||
? resources.texture('image-float16', 'rgba', 'fp16', filter)
|
||||
: type === 'float32'
|
||||
? resources.texture('image-float32', 'rgba', 'float', filter)
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', filter);
|
||||
// make a depth renderbuffer of the same size as the targetTexture
|
||||
const depthRenderbuffer = depth
|
||||
? resources.renderbuffer('depth16', 'depth', _width, _height)
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -45,6 +45,12 @@ interface Resource {
|
||||
|
||||
type ResourceName = keyof WebGLStats['resourceCounts']
|
||||
|
||||
type ByteCounts = {
|
||||
texture: number
|
||||
attribute: number
|
||||
elements: number
|
||||
}
|
||||
|
||||
export interface WebGLResources {
|
||||
attribute: (array: ArrayType, itemSize: AttributeItemSize, divisor: number, usageHint?: UsageHint) => AttributeBuffer
|
||||
elements: (array: ElementsType, usageHint?: UsageHint) => ElementsBuffer
|
||||
@@ -55,6 +61,8 @@ export interface WebGLResources {
|
||||
texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => Texture,
|
||||
vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => VertexArray,
|
||||
|
||||
getByteCounts: () => ByteCounts
|
||||
|
||||
reset: () => void
|
||||
destroy: () => void
|
||||
}
|
||||
@@ -128,6 +136,25 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
|
||||
return wrap('vertexArray', createVertexArray(extensions, program, attributeBuffers, elementsBuffer));
|
||||
},
|
||||
|
||||
getByteCounts: () => {
|
||||
let texture = 0;
|
||||
sets.texture.forEach(r => {
|
||||
texture += (r as Texture).getByteCount();
|
||||
});
|
||||
|
||||
let attribute = 0;
|
||||
sets.attribute.forEach(r => {
|
||||
attribute += (r as AttributeBuffer).length * 4;
|
||||
});
|
||||
|
||||
let elements = 0;
|
||||
sets.elements.forEach(r => {
|
||||
elements += (r as ElementsBuffer).length * 4;
|
||||
});
|
||||
|
||||
return { texture, attribute, elements };
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
sets.attribute.forEach(r => r.reset());
|
||||
sets.elements.forEach(r => r.reset());
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -19,14 +19,16 @@ const getNextTextureId = idFactory();
|
||||
export type TextureKindValue = {
|
||||
'image-uint8': TextureImage<Uint8Array>
|
||||
'image-float32': TextureImage<Float32Array>
|
||||
'image-float16': TextureImage<Float32Array>
|
||||
'image-depth': TextureImage<Uint8Array> // TODO should be Uint32Array
|
||||
'volume-uint8': TextureVolume<Uint8Array>
|
||||
'volume-float32': TextureVolume<Float32Array>
|
||||
'volume-float16': TextureVolume<Float32Array>
|
||||
'texture': Texture
|
||||
}
|
||||
export type TextureValueType = ValueOf<TextureKindValue>
|
||||
export type TextureKind = keyof TextureKindValue
|
||||
export type TextureType = 'ubyte' | 'ushort' | 'float'
|
||||
export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16'
|
||||
export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
|
||||
/** Numbers are shortcuts for color attachment */
|
||||
export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
@@ -36,12 +38,14 @@ export function getTarget(gl: GLRenderingContext, kind: TextureKind): number {
|
||||
switch (kind) {
|
||||
case 'image-uint8': return gl.TEXTURE_2D;
|
||||
case 'image-float32': return gl.TEXTURE_2D;
|
||||
case 'image-float16': return gl.TEXTURE_2D;
|
||||
case 'image-depth': return gl.TEXTURE_2D;
|
||||
}
|
||||
if (isWebGL2(gl)) {
|
||||
switch (kind) {
|
||||
case 'volume-uint8': return gl.TEXTURE_3D;
|
||||
case 'volume-float32': return gl.TEXTURE_3D;
|
||||
case 'volume-float16': return gl.TEXTURE_3D;
|
||||
}
|
||||
}
|
||||
throw new Error(`unknown texture kind '${kind}'`);
|
||||
@@ -65,16 +69,19 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.ALPHA;
|
||||
case 'float': return gl.R32F;
|
||||
case 'fp16': return gl.R16F;
|
||||
}
|
||||
case 'rgb':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RGB;
|
||||
case 'float': return gl.RGB32F;
|
||||
case 'fp16': return gl.RGB16F;
|
||||
}
|
||||
case 'rgba':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RGBA;
|
||||
case 'float': return gl.RGBA32F;
|
||||
case 'fp16': return gl.RGBA16F;
|
||||
}
|
||||
case 'depth':
|
||||
return gl.DEPTH_COMPONENT16;
|
||||
@@ -83,11 +90,37 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
|
||||
return getFormat(gl, format, type);
|
||||
}
|
||||
|
||||
export function getType(gl: GLRenderingContext, type: TextureType): number {
|
||||
function getByteCount(format: TextureFormat, type: TextureType, width: number, height: number, depth: number): number {
|
||||
const bpe = getFormatSize(format) * getTypeSize(type);
|
||||
return bpe * width * height * (depth || 1);
|
||||
}
|
||||
|
||||
function getFormatSize(format: TextureFormat) {
|
||||
switch (format) {
|
||||
case 'alpha': return 1;
|
||||
case 'rgb': return 3;
|
||||
case 'rgba': return 4;
|
||||
case 'depth': return 4;
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeSize(type: TextureType): number {
|
||||
switch (type) {
|
||||
case 'ubyte': return 1;
|
||||
case 'ushort': return 2;
|
||||
case 'float': return 4;
|
||||
case 'fp16': return 2;
|
||||
}
|
||||
}
|
||||
|
||||
export function getType(gl: GLRenderingContext, extensions: WebGLExtensions, type: TextureType): number {
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.UNSIGNED_BYTE;
|
||||
case 'ushort': return gl.UNSIGNED_SHORT;
|
||||
case 'float': return gl.FLOAT;
|
||||
case 'fp16':
|
||||
if (extensions.textureHalfFloat) return extensions.textureHalfFloat.HALF_FLOAT;
|
||||
else throw new Error('extension "texture_half_float" unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +170,14 @@ export interface Texture {
|
||||
getHeight: () => number
|
||||
getDepth: () => number
|
||||
|
||||
getByteCount: () => number
|
||||
|
||||
define: (width: number, height: number, depth?: number) => void
|
||||
load: (image: TextureImage<any> | TextureVolume<any>) => void
|
||||
/**
|
||||
* The `sub` option requires an existing allocation on the GPU, that is, either
|
||||
* `define` or `load` without `sub` must have been called before.
|
||||
*/
|
||||
load: (image: TextureImage<any> | TextureVolume<any>, sub?: boolean) => void
|
||||
bind: (id: TextureId) => void
|
||||
unbind: (id: TextureId) => void
|
||||
/** Use `layer` to attach a z-slice of a 3D texture */
|
||||
@@ -169,6 +208,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
// check texture kind and type compatability
|
||||
if (
|
||||
(kind.endsWith('float32') && _type !== 'float') ||
|
||||
(kind.endsWith('float16') && _type !== 'fp16') ||
|
||||
(kind.endsWith('uint8') && _type !== 'ubyte') ||
|
||||
(kind.endsWith('depth') && _type !== 'ushort')
|
||||
) {
|
||||
@@ -179,7 +219,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
const filter = getFilter(gl, _filter);
|
||||
const format = getFormat(gl, _format, _type);
|
||||
const internalFormat = getInternalFormat(gl, _format, _type);
|
||||
const type = getType(gl, _type);
|
||||
const type = getType(gl, extensions, _type);
|
||||
|
||||
function init() {
|
||||
gl.bindTexture(target, texture);
|
||||
@@ -210,7 +250,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
}
|
||||
}
|
||||
|
||||
function load(data: TextureImage<any> | TextureVolume<any>) {
|
||||
function load(data: TextureImage<any> | TextureVolume<any>, sub = false) {
|
||||
gl.bindTexture(target, texture);
|
||||
// unpack alignment of 1 since we use textures only for data
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
@@ -218,12 +258,20 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
||||
if (isTexture2d(data, target, gl)) {
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
|
||||
width = data.width, height = data.height;
|
||||
gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, data.array);
|
||||
if (sub) {
|
||||
gl.texSubImage2D(target, 0, 0, 0, data.width, data.height, format, type, data.array);
|
||||
} else {
|
||||
width = data.width, height = data.height;
|
||||
gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, data.array);
|
||||
}
|
||||
} else if (isWebGL2(gl) && isTexture3d(data, target, gl)) {
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
width = data.width, height = data.height, depth = data.depth;
|
||||
gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, data.array);
|
||||
if (sub) {
|
||||
gl.texSubImage3D(target, 0, 0, 0, 0, data.width, data.height, data.depth, format, type, data.array);
|
||||
} else {
|
||||
width = data.width, height = data.height, depth = data.depth;
|
||||
gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, data.array);
|
||||
}
|
||||
} else {
|
||||
throw new Error('unknown texture target');
|
||||
}
|
||||
@@ -254,6 +302,8 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
getHeight: () => height,
|
||||
getDepth: () => depth,
|
||||
|
||||
getByteCount: () => getByteCount(_format, _type, width, height, depth),
|
||||
|
||||
define,
|
||||
load,
|
||||
bind: (id: TextureId) => {
|
||||
@@ -279,11 +329,10 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
texture = getTexture(gl);
|
||||
init();
|
||||
|
||||
if (loadedData) {
|
||||
load(loadedData);
|
||||
} else {
|
||||
define(width, height, depth);
|
||||
}
|
||||
const [_width, _height, _depth] = [width, height, depth];
|
||||
width = 0, height = 0, depth = 0; // set to zero to trigger resize
|
||||
define(_width, _height, _depth);
|
||||
if (loadedData) load(loadedData);
|
||||
},
|
||||
destroy: () => {
|
||||
if (destroyed) return;
|
||||
@@ -328,6 +377,7 @@ export function createNullTexture(gl: GLRenderingContext, kind: TextureKind): Te
|
||||
getWidth: () => 0,
|
||||
getHeight: () => 0,
|
||||
getDepth: () => 0,
|
||||
getByteCount: () => 0,
|
||||
|
||||
define: () => {},
|
||||
load: () => {},
|
||||
|
||||
@@ -79,4 +79,30 @@ export function getUniformSetters(schema: RenderableSchema) {
|
||||
}
|
||||
});
|
||||
return setters;
|
||||
}
|
||||
|
||||
export function getUniformGlslType(kind: UniformKind): string {
|
||||
switch (kind) {
|
||||
case 'f': return 'float';
|
||||
case 'i': return 'int';
|
||||
case 't': return 'sampler2D';
|
||||
case 'b': return 'bool';
|
||||
case 'v2': return 'vec2';
|
||||
case 'v3': return 'vec3';
|
||||
case 'v4': return 'vec4';
|
||||
case 'm3': return 'mat3';
|
||||
case 'm4': return 'mat4';
|
||||
}
|
||||
throw new Error(`${kind} has no primitive GLSL type.`);
|
||||
}
|
||||
|
||||
export function isUniformValueScalar(kind: UniformKind): boolean {
|
||||
switch (kind) {
|
||||
case 'f':
|
||||
case 'i':
|
||||
case 'b':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ReaderResult as Result } from '../result';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { parseCsv } from '../csv/parser';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { toTable } from '../cif/schema';
|
||||
|
||||
import Schema = Column.Schema
|
||||
import { CsvTable } from '../csv/data-model';
|
||||
|
||||
|
||||
export const Schema3DG = {
|
||||
/** Chromosome name */
|
||||
chromosome: Schema.str,
|
||||
/** Base position */
|
||||
position: Schema.int,
|
||||
/** X coordinate */
|
||||
x: Schema.float,
|
||||
/** Y coordinate */
|
||||
y: Schema.float,
|
||||
/** Z coordinate */
|
||||
z: Schema.float,
|
||||
};
|
||||
export type Schema3DG = typeof Schema3DG
|
||||
|
||||
export interface File3DG {
|
||||
table: Table<Schema3DG>
|
||||
}
|
||||
|
||||
const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ];
|
||||
|
||||
function categoryFromTable(name: string, table: CsvTable) {
|
||||
return {
|
||||
name,
|
||||
rowCount: table.rowCount,
|
||||
fieldNames: FieldNames,
|
||||
getField: (name: string) => {
|
||||
return table.getColumn(FieldNames.indexOf(name).toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function parse3DG(data: string) {
|
||||
return Task.create<Result<File3DG>>('Parse 3DG', async ctx => {
|
||||
const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true };
|
||||
const csvFile = await parseCsv(data, opts).runInContext(ctx);
|
||||
if (csvFile.isError) return Result.error(csvFile.message, csvFile.line);
|
||||
const category = categoryFromTable('3dg', csvFile.result.table);
|
||||
const table = toTable(Schema3DG, category);
|
||||
return Result.success({ table });
|
||||
});
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { parse3DG } from '../3dg/parser';
|
||||
|
||||
const basic3dgString = `1(mat) 1420000 0.791377837067 10.9947291355 -13.1882897693
|
||||
1(mat) 1440000 -0.268241283699 10.5200875887 -13.0896257278
|
||||
1(mat) 1460000 -1.3853075236 10.5513787498 -13.1440142173
|
||||
1(mat) 1480000 -1.55984101733 11.4340829129 -13.6026301209
|
||||
1(mat) 1500000 -0.770991778399 11.4758488546 -14.5881137222
|
||||
1(mat) 1520000 -0.0848245107875 12.2624690808 -14.354289628
|
||||
1(mat) 1540000 -0.458643807046 12.5985791771 -13.4701149287
|
||||
1(mat) 1560000 -0.810322906201 12.2461643989 -12.3172933413
|
||||
1(mat) 1580000 -2.08211172035 12.8886838656 -12.8742007778
|
||||
1(mat) 1600000 -3.52093948201 13.1850935438 -12.4118684428`;
|
||||
|
||||
describe('3dg reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await parse3DG(basic3dgString).run();
|
||||
expect(parsed.isError).toBe(false);
|
||||
|
||||
if (parsed.isError) return;
|
||||
const { chromosome, position, x, y, z } = parsed.result.table;
|
||||
expect(chromosome.value(0)).toBe('1(mat)');
|
||||
expect(position.value(1)).toBe(1440000);
|
||||
expect(x.value(5)).toBe(-0.0848245107875);
|
||||
expect(y.value(5)).toBe(12.2624690808);
|
||||
expect(z.value(5)).toBe(-14.354289628);
|
||||
});
|
||||
});
|
||||
@@ -90,6 +90,7 @@ export class MolEncoder extends LigandEncoder {
|
||||
const { instance, source } = getCategoryInstanceData(category, context);
|
||||
const fields = instance.fields;
|
||||
const src = source[0];
|
||||
if (!src) return;
|
||||
const data = src.data;
|
||||
|
||||
const it = src.keys();
|
||||
|
||||
@@ -289,6 +289,7 @@ export class Mol2Encoder extends LigandEncoder {
|
||||
const { instance, source } = getCategoryInstanceData(category, context);
|
||||
const fields = instance.fields;
|
||||
const src = source[0];
|
||||
if (!src) return;
|
||||
const data = src.data;
|
||||
|
||||
const it = src.keys();
|
||||
|
||||
@@ -37,6 +37,15 @@ export type DensityTextureData = {
|
||||
gridTexScale: Vec2
|
||||
}
|
||||
|
||||
export interface RegularGrid3d {
|
||||
box: Box3D,
|
||||
dimensions: Vec3
|
||||
}
|
||||
|
||||
export function getRegularGrid3dDelta({ box, dimensions }: RegularGrid3d) {
|
||||
return Vec3.div(Vec3(), Box3D.size(Vec3(), box), Vec3.subScalar(Vec3(), dimensions, 1));
|
||||
}
|
||||
|
||||
export function fillGridDim(length: number, start: number, step: number) {
|
||||
const a = new Float32Array(length);
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Box3D } from '../geometry';
|
||||
import { Box3D, DensityData, DensityTextureData } from '../geometry';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { PositionData, DensityData } from './common';
|
||||
import { PositionData } from './common';
|
||||
import { GaussianDensityCPU } from './gaussian-density/cpu';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
@@ -33,13 +33,21 @@ export const DefaultGaussianDensityProps = {
|
||||
};
|
||||
export type GaussianDensityProps = typeof DefaultGaussianDensityProps
|
||||
|
||||
export type GaussianDensityData = {
|
||||
radiusFactor: number
|
||||
} & DensityData
|
||||
|
||||
export type GaussianDensityTextureData = {
|
||||
radiusFactor: number
|
||||
} & DensityTextureData
|
||||
|
||||
export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl?: WebGLContext) {
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return await GaussianDensity(ctx, position, box, radius, props, webgl);
|
||||
});
|
||||
}
|
||||
|
||||
export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl?: WebGLContext): Promise<DensityData> {
|
||||
export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl?: WebGLContext): Promise<GaussianDensityData> {
|
||||
if (props.useGpu) {
|
||||
if (!GaussianDensityGPU) throw 'GPU computation not supported on this platform';
|
||||
if (!webgl) throw 'No WebGL context provided';
|
||||
@@ -65,7 +73,7 @@ function _computeGaussianDensityTexture(type: '2d' | '3d', position: PositionDat
|
||||
if (!GaussianDensityTexture) throw 'GPU computation not supported on this platform';
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return type === '2d' ?
|
||||
GaussianDensityTexture2d(webgl, position, box, radius, props, texture) :
|
||||
GaussianDensityTexture2d(webgl, position, box, radius, false, props, texture) :
|
||||
GaussianDensityTexture3d(webgl, position, box, radius, props, texture);
|
||||
});
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
import { Box3D, fillGridDim } from '../../geometry';
|
||||
import { Vec3, Mat4, Tensor } from '../../linear-algebra';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { PositionData, DensityData } from '../common';
|
||||
import { PositionData } from '../common';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { GaussianDensityProps } from '../gaussian-density';
|
||||
import { GaussianDensityProps, GaussianDensityData } from '../gaussian-density';
|
||||
import { fasterExp } from '../../approx';
|
||||
|
||||
export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
|
||||
export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<GaussianDensityData> {
|
||||
const { resolution, radiusOffset, smoothness } = props;
|
||||
const scaleFactor = 1 / resolution;
|
||||
|
||||
@@ -129,5 +129,5 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
|
||||
Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
|
||||
Mat4.setTranslation(transform, expandedBox.min);
|
||||
|
||||
return { field, idField, transform };
|
||||
return { field, idField, transform, radiusFactor: 1 };
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
*/
|
||||
|
||||
import { PositionData, DensityData, DensityTextureData } from '../common';
|
||||
import { PositionData } from '../common';
|
||||
import { Box3D } from '../../geometry';
|
||||
import { GaussianDensityGPUProps } from '../gaussian-density';
|
||||
import { GaussianDensityGPUProps, GaussianDensityData, GaussianDensityTextureData } from '../gaussian-density';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
@@ -40,7 +40,7 @@ export const GaussianDensitySchema = {
|
||||
uGridTexScale: UniformSpec('v2', true),
|
||||
uAlpha: UniformSpec('f', true),
|
||||
uResolution: UniformSpec('f', true),
|
||||
uRadiusFactor: UniformSpec('f', true),
|
||||
uRadiusFactorInv: UniformSpec('f', true),
|
||||
tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
|
||||
dGridTexType: DefineSpec('string', ['2d', '3d']),
|
||||
@@ -68,35 +68,35 @@ function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format
|
||||
return webgl.namedTextures[_name];
|
||||
}
|
||||
|
||||
export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData {
|
||||
export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): GaussianDensityData {
|
||||
// always use texture2d when the gaussian density needs to be downloaded from the GPU,
|
||||
// it's faster than texture3d
|
||||
// console.time('GaussianDensityTexture2d')
|
||||
const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, tmpTexture);
|
||||
const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
|
||||
// webgl.waitForGpuCommandsCompleteSync()
|
||||
// console.timeEnd('GaussianDensityTexture2d')
|
||||
const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
|
||||
|
||||
return { field, idField, transform: getTransform(scale, bbox) };
|
||||
return { field, idField, transform: getTransform(scale, bbox), radiusFactor };
|
||||
}
|
||||
|
||||
export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
|
||||
export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
|
||||
return webgl.isWebGL2 ?
|
||||
GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) :
|
||||
GaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture);
|
||||
GaussianDensityTexture2d(webgl, position, box, radius, false, props, oldTexture);
|
||||
}
|
||||
|
||||
export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
|
||||
return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture));
|
||||
export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
|
||||
return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture));
|
||||
}
|
||||
|
||||
export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
|
||||
export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
|
||||
return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
|
||||
}
|
||||
|
||||
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale }: GaussianDensityTextureData): DensityTextureData {
|
||||
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale };
|
||||
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor }: _GaussianDensityTextureData): GaussianDensityTextureData {
|
||||
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor };
|
||||
}
|
||||
|
||||
function getTransform(scale: Vec3, bbox: Box3D) {
|
||||
@@ -108,30 +108,34 @@ function getTransform(scale: Vec3, bbox: Box3D) {
|
||||
|
||||
//
|
||||
|
||||
type GaussianDensityTextureData = {
|
||||
type _GaussianDensityTextureData = {
|
||||
texture: Texture,
|
||||
scale: Vec3,
|
||||
bbox: Box3D,
|
||||
gridDim: Vec3,
|
||||
gridTexDim: Vec3
|
||||
gridTexScale: Vec2
|
||||
radiusFactor: number
|
||||
}
|
||||
|
||||
function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
|
||||
function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
|
||||
// console.log('2d');
|
||||
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
|
||||
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = webgl;
|
||||
const { smoothness, resolution } = props;
|
||||
|
||||
const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
|
||||
const [ dx, dy, dz ] = dim;
|
||||
const { texDimX, texDimY, texCols } = getTexture2dSize(dim);
|
||||
// console.log({ texDimX, texDimY, texCols, texSize, dim });
|
||||
const { texDimX, texDimY, texCols, powerOfTwoSize } = getTexture2dSize(dim);
|
||||
// console.log({ texDimX, texDimY, texCols, powerOfTwoSize, dim });
|
||||
const gridTexDim = Vec3.create(texDimX, texDimY, 0);
|
||||
const gridTexScale = Vec2.create(texDimX / texDimX, texDimY / texDimY);
|
||||
const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize);
|
||||
const radiusFactor = maxRadius * 2;
|
||||
|
||||
const width = powerOfTwo ? powerOfTwoSize : texDimX;
|
||||
const height = powerOfTwo ? powerOfTwoSize : texDimY;
|
||||
|
||||
const minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
minDistTex.define(texDimX, texDimY);
|
||||
minDistTex.define(width, height);
|
||||
|
||||
const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
|
||||
|
||||
@@ -143,17 +147,24 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
framebuffer.bind();
|
||||
setRenderingDefaults(webgl);
|
||||
|
||||
if (!texture) texture = colorBufferFloat && textureFloat
|
||||
? resources.texture('image-float32', 'rgba', 'float', 'linear')
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
texture.define(texDimX, texDimY);
|
||||
if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
|
||||
? resources.texture('image-float16', 'rgba', 'fp16', 'linear')
|
||||
: colorBufferFloat && textureFloat
|
||||
? resources.texture('image-float32', 'rgba', 'float', 'linear')
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
texture.define(width, height);
|
||||
|
||||
// console.log(renderable)
|
||||
|
||||
function render(fbTex: Texture, clear: boolean) {
|
||||
state.currentRenderItemId = -1;
|
||||
fbTex.attachFramebuffer(framebuffer, 0);
|
||||
if (clear) gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
if (clear) {
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.scissor(0, 0, width, height);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
ValueCell.update(uCurrentY, 0);
|
||||
let currCol = 0;
|
||||
let currY = 0;
|
||||
let currX = 0;
|
||||
@@ -164,10 +175,11 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
currX = 0;
|
||||
ValueCell.update(uCurrentY, currY);
|
||||
}
|
||||
// console.log({ i, currX, currY })
|
||||
// console.log({ i, currX, currY });
|
||||
ValueCell.update(uCurrentX, currX);
|
||||
ValueCell.update(uCurrentSlice, i);
|
||||
gl.viewport(currX, currY, dx, dy);
|
||||
gl.scissor(currX, currY, dx, dy);
|
||||
renderable.render();
|
||||
++currCol;
|
||||
currX += dx;
|
||||
@@ -184,14 +196,14 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
setupGroupIdRendering(webgl, renderable);
|
||||
render(texture, false);
|
||||
|
||||
// printTexture(webgl, texture, 1);
|
||||
// printTexture(webgl, minDistTex, 0.75);
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale };
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor };
|
||||
}
|
||||
|
||||
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
|
||||
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
|
||||
// console.log('3d');
|
||||
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
|
||||
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = webgl;
|
||||
const { smoothness, resolution } = props;
|
||||
|
||||
const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
|
||||
@@ -213,10 +225,13 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
|
||||
framebuffer.bind();
|
||||
setRenderingDefaults(webgl);
|
||||
gl.viewport(0, 0, dx, dy);
|
||||
gl.scissor(0, 0, dx, dy);
|
||||
|
||||
if (!texture) texture = colorBufferFloat && textureFloat
|
||||
? resources.texture('volume-float32', 'rgba', 'float', 'linear')
|
||||
: resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
|
||||
if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
|
||||
? resources.texture('volume-float16', 'rgba', 'fp16', 'linear')
|
||||
: colorBufferFloat && textureFloat
|
||||
? resources.texture('volume-float32', 'rgba', 'float', 'linear')
|
||||
: resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
|
||||
texture.define(dx, dy, dz);
|
||||
|
||||
function render(fbTex: Texture, clear: boolean) {
|
||||
@@ -239,7 +254,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
|
||||
setupGroupIdRendering(webgl, renderable);
|
||||
render(texture, false);
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale };
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor };
|
||||
}
|
||||
|
||||
//
|
||||
@@ -303,7 +318,7 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po
|
||||
ValueCell.update(v.uGridTexScale, gridTexScale);
|
||||
ValueCell.updateIfChanged(v.uAlpha, smoothness);
|
||||
ValueCell.updateIfChanged(v.uResolution, resolution);
|
||||
ValueCell.updateIfChanged(v.uRadiusFactor, radiusFactor);
|
||||
ValueCell.updateIfChanged(v.uRadiusFactorInv, 1 / radiusFactor);
|
||||
ValueCell.update(v.tMinDistanceTex, minDistanceTexture);
|
||||
|
||||
ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d');
|
||||
@@ -337,7 +352,7 @@ function createGaussianDensityRenderable(webgl: WebGLContext, drawCount: number,
|
||||
uGridTexScale: ValueCell.create(gridTexScale),
|
||||
uAlpha: ValueCell.create(smoothness),
|
||||
uResolution: ValueCell.create(resolution),
|
||||
uRadiusFactor: ValueCell.create(radiusFactor),
|
||||
uRadiusFactorInv: ValueCell.create(1 / radiusFactor),
|
||||
tMinDistanceTex: ValueCell.create(minDistanceTexture),
|
||||
|
||||
dGridTexType: ValueCell.create(minDistanceTexture.getDepth() > 0 ? '3d' : '2d'),
|
||||
@@ -355,7 +370,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.disable(gl.CULL_FACE);
|
||||
state.enable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
@@ -409,7 +424,8 @@ function getTexture2dSize(gridDim: Vec3) {
|
||||
} else {
|
||||
texDimX = gridDim[0] * gridDim[2];
|
||||
}
|
||||
return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY };
|
||||
// console.log(texDimX, texDimY, texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2);
|
||||
return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
|
||||
}
|
||||
|
||||
export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { File3DG } from '../../mol-io/reader/3dg/parser';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { createModels } from './basic/parser';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
|
||||
function getBasic(table: File3DG['table']) {
|
||||
const entityIds = new Array<string>(table._rowCount);
|
||||
const entityBuilder = new EntityBuilder();
|
||||
|
||||
const seqIdStarts = table.position.toArray({ array: Uint32Array });
|
||||
const seqIdEnds = new Uint32Array(table._rowCount);
|
||||
const stride = seqIdStarts[1] - seqIdStarts[0];
|
||||
|
||||
const objectRadius = stride / 3500;
|
||||
|
||||
for (let i = 0, il = table._rowCount; i < il; ++i) {
|
||||
const chr = table.chromosome.value(i);
|
||||
const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr);
|
||||
entityIds[i] = entityId;
|
||||
seqIdEnds[i] = seqIdStarts[i] + stride - 1;
|
||||
}
|
||||
|
||||
const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, {
|
||||
id: Column.ofIntArray(fillSerial(new Uint32Array(table._rowCount))),
|
||||
entity_id: Column.ofStringArray(entityIds),
|
||||
seq_id_begin: Column.ofIntArray(seqIdStarts),
|
||||
seq_id_end: Column.ofIntArray(seqIdEnds),
|
||||
asym_id: table.chromosome,
|
||||
|
||||
Cartn_x: Column.ofFloatArray(Column.mapToArray(table.x, x => x * 10, Float32Array)),
|
||||
Cartn_y: Column.ofFloatArray(Column.mapToArray(table.y, y => y * 10, Float32Array)),
|
||||
Cartn_z: Column.ofFloatArray(Column.mapToArray(table.z, z => z * 10, Float32Array)),
|
||||
|
||||
object_radius: Column.ofConst(objectRadius, table._rowCount, Column.Schema.float),
|
||||
rmsf: Column.ofConst(0, table._rowCount, Column.Schema.float),
|
||||
model_id: Column.ofConst(1, table._rowCount, Column.Schema.int),
|
||||
}, table._rowCount);
|
||||
|
||||
return createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
|
||||
model_id: Column.ofIntArray([1]),
|
||||
model_name: Column.ofStringArray(['3DG Model']),
|
||||
}, 1),
|
||||
ihm_sphere_obj_site
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { Format3dg };
|
||||
|
||||
type Format3dg = ModelFormat<File3DG>
|
||||
|
||||
namespace Format3dg {
|
||||
export function is(x: ModelFormat): x is Format3dg {
|
||||
return x.kind === '3dg';
|
||||
}
|
||||
|
||||
export function from3dg(file3dg: File3DG): Format3dg {
|
||||
return { kind: '3dg', name: '3DG', data: file3dg };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFrom3DG(file3dg: File3DG): Task<Trajectory> {
|
||||
return Task.create('Parse 3DG', async ctx => {
|
||||
const format = Format3dg.from3dg(file3dg);
|
||||
const basic = getBasic(file3dg.table);
|
||||
return createModels(basic, format, ctx);
|
||||
});
|
||||
}
|
||||
@@ -24,9 +24,9 @@ export type ChemComp = Table<mmCIF_chemComp_schema>
|
||||
export type ChemCompIdentifier = Table<mmCIF_Schema['pdbx_chem_comp_identifier']>
|
||||
export type AtomSite = Table<mmCIF_Schema['atom_site']>
|
||||
export type IhmSphereObjSite = Table<mmCIF_Schema['ihm_sphere_obj_site']>
|
||||
export type IhmGaussianObjSite =Table<mmCIF_Schema['ihm_gaussian_obj_site']>
|
||||
export type UnobsOrZeroOccResidues =Table<mmCIF_Schema['pdbx_unobs_or_zero_occ_residues']>
|
||||
export type Molecule =Table<mmCIF_Schema['pdbx_molecule']>
|
||||
export type IhmGaussianObjSite = Table<mmCIF_Schema['ihm_gaussian_obj_site']>
|
||||
export type UnobsOrZeroOccResidues = Table<mmCIF_Schema['pdbx_unobs_or_zero_occ_residues']>
|
||||
export type Molecule = Table<mmCIF_Schema['pdbx_molecule']>
|
||||
|
||||
export const BasicSchema = {
|
||||
entry: mmCIF_Schema.entry,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { AtomSiteAnisotrop } from './property/anisotropic';
|
||||
import { ComponentBond } from './property/bonds/chem_comp';
|
||||
import { StructConn } from './property/bonds/struct_conn';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
function modelSymmetryFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -69,6 +70,8 @@ function structConnFromMmcif(model: Model) {
|
||||
}
|
||||
StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif);
|
||||
|
||||
GlobalModelTransformInfo.Provider.formatRegistry.add('mmCIF', GlobalModelTransformInfo.fromMmCif, GlobalModelTransformInfo.hasData);
|
||||
|
||||
//
|
||||
|
||||
export { MmcifFormat };
|
||||
|
||||
89
src/mol-model-formats/structure/pdb/conect.ts
Normal file
89
src/mol-model-formats/structure/pdb/conect.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CifCategory, CifField } from '../../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Tokens } from '../../../mol-io/reader/common/text/tokenizer';
|
||||
|
||||
export function parseConect(lines: Tokens, lineStart: number, lineEnd: number, sites: { [K in keyof mmCIF_Schema['atom_site']]?: CifField }): CifCategory {
|
||||
const idMap: { [k: string]: number } = {};
|
||||
for (let i = 0, il = sites.id!.rowCount; i < il; ++i) {
|
||||
idMap[sites.id!.str(i)] = i;
|
||||
}
|
||||
|
||||
const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1]);
|
||||
|
||||
const id: string[] = [];
|
||||
const conn_type_id: string[] = [];
|
||||
|
||||
const ptnr1_label_asym_id: string[] = [];
|
||||
const ptnr1_label_seq_id: number[] = [];
|
||||
const ptnr1_auth_seq_id: number[] = [];
|
||||
const ptnr1_label_atom_id: string[] = [];
|
||||
|
||||
const ptnr2_label_asym_id: string[] = [];
|
||||
const ptnr2_label_seq_id: number[] = [];
|
||||
const ptnr2_auth_seq_id: number[] = [];
|
||||
const ptnr2_label_atom_id: string[] = [];
|
||||
|
||||
const pos = [11, 16, 21, 26];
|
||||
|
||||
let k = 1;
|
||||
|
||||
for (let i = lineStart; i < lineEnd; i++) {
|
||||
const line = getLine(i);
|
||||
const idxA = idMap[parseInt(line.substr(6, 5))];
|
||||
|
||||
const bondIndex: {[k: number]: number} = {};
|
||||
|
||||
if (idxA === undefined) continue;
|
||||
|
||||
for (let j = 0; j < 4; ++j) {
|
||||
const idB = parseInt(line.substr(pos[j], 5));
|
||||
if (Number.isNaN(idB)) continue;
|
||||
|
||||
const idxB = idMap[idB];
|
||||
if (idxB === undefined) continue;
|
||||
if (idxA > idxB) continue;
|
||||
|
||||
// TODO: interpret records where a 'idxB' atom is given multiple times
|
||||
// as double/triple bonds, e.g. CONECT 1529 1528 1528 is a double bond
|
||||
if (bondIndex[idxB] !== undefined) continue;
|
||||
|
||||
id.push(`covale${k}`);
|
||||
conn_type_id.push('covale');
|
||||
|
||||
ptnr1_label_asym_id.push(sites.label_asym_id!.str(idxA));
|
||||
ptnr1_auth_seq_id.push(sites.auth_seq_id!.int(idxA));
|
||||
ptnr1_label_seq_id.push(sites.label_seq_id!.int(idxA));
|
||||
ptnr1_label_atom_id.push(sites.label_atom_id!.str(idxA));
|
||||
|
||||
ptnr2_label_asym_id.push(sites.label_asym_id!.str(idxB));
|
||||
ptnr2_auth_seq_id.push(sites.auth_seq_id!.int(idxB));
|
||||
ptnr2_label_seq_id.push(sites.label_seq_id!.int(idxB));
|
||||
ptnr2_label_atom_id.push(sites.label_atom_id!.str(idxB));
|
||||
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const struct_conn: Partial<CifCategory.Fields<mmCIF_Schema['struct_conn']>> = {
|
||||
id: CifField.ofStrings(id),
|
||||
conn_type_id: CifField.ofStrings(conn_type_id),
|
||||
|
||||
ptnr1_label_asym_id: CifField.ofStrings(ptnr1_label_asym_id),
|
||||
ptnr1_auth_seq_id: CifField.ofNumbers(ptnr1_auth_seq_id),
|
||||
ptnr1_label_seq_id: CifField.ofNumbers(ptnr1_label_seq_id),
|
||||
ptnr1_label_atom_id: CifField.ofStrings(ptnr1_label_atom_id),
|
||||
|
||||
ptnr2_label_asym_id: CifField.ofStrings(ptnr2_label_asym_id),
|
||||
ptnr2_label_seq_id: CifField.ofNumbers(ptnr2_label_seq_id),
|
||||
ptnr2_auth_seq_id: CifField.ofNumbers(ptnr2_auth_seq_id),
|
||||
ptnr2_label_atom_id: CifField.ofStrings(ptnr2_label_atom_id),
|
||||
};
|
||||
|
||||
return CifCategory.ofFields('struct_conn', struct_conn);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -18,6 +18,8 @@ import { Column } from '../../../mol-data/db';
|
||||
import { getMoleculeType } from '../../../mol-model/structure/model/types';
|
||||
import { getAtomSiteTemplate, addAtom, getAtomSite } from './atom-site';
|
||||
import { addAnisotropic, getAnisotropicTemplate, getAnisotropic } from './anisotropic';
|
||||
import { parseConect } from './conect';
|
||||
import { isDebugMode } from '../../../mol-util/debug';
|
||||
|
||||
export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
const { lines } = pdb;
|
||||
@@ -48,6 +50,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
const heteroNames: [string, string][] = [];
|
||||
|
||||
let modelNum = 0, modelStr = '';
|
||||
let conectRange: [number, number] | undefined = undefined;
|
||||
|
||||
for (let i = 0, _i = lines.count; i < _i; i++) {
|
||||
let s = indices[2 * i], e = indices[2 * i + 1];
|
||||
@@ -63,8 +66,21 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
case 'C':
|
||||
if (substringStartsWith(data, s, e, 'CRYST1')) {
|
||||
helperCategories.push(...parseCryst1(pdb.id || '?', data.substring(s, e)));
|
||||
} else if (substringStartsWith(data, s, e, 'CONNECT')) {
|
||||
// TODO: CONNECT records => struct_conn
|
||||
} else if (substringStartsWith(data, s, e, 'CONECT')) {
|
||||
let j = i + 1;
|
||||
while (true) {
|
||||
s = indices[2 * j]; e = indices[2 * j + 1];
|
||||
if (!substringStartsWith(data, s, e, 'CONECT')) break;
|
||||
j++;
|
||||
}
|
||||
if (conectRange) {
|
||||
if (isDebugMode) {
|
||||
console.log('only single CONECT block allowed, ignoring others');
|
||||
}
|
||||
} else {
|
||||
conectRange = [i, j];
|
||||
}
|
||||
i = j - 1;
|
||||
} else if (substringStartsWith(data, s, e, 'COMPND')) {
|
||||
let j = i + 1;
|
||||
while (true) {
|
||||
@@ -165,6 +181,10 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
const atom_site = getAtomSite(atomSite);
|
||||
if (!isPdbqt) delete atom_site.partial_charge;
|
||||
|
||||
if (conectRange) {
|
||||
helperCategories.push(parseConect(lines, conectRange[0], conectRange[1], atom_site));
|
||||
}
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofTable('entity', entityBuilder.getEntityTable()),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', componentBuilder.getChemCompTable()),
|
||||
|
||||
@@ -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>
|
||||
@@ -52,6 +52,15 @@ export namespace StructConn {
|
||||
|
||||
export const Provider = FormatPropertyProvider.create<StructConn>(Descriptor);
|
||||
|
||||
/**
|
||||
* Heuristic to test if StructConn likely provides all atomic bonds by
|
||||
* checking if the fraction of bonds and atoms is high (> 0.95).
|
||||
*/
|
||||
export function isExhaustive(model: Model): boolean {
|
||||
const structConn = StructConn.Provider.get(model);
|
||||
return !!structConn && (structConn.data.id.rowCount / model.atomicConformation.atomId.rowCount) > 0.95;
|
||||
}
|
||||
|
||||
function hasAtom({ units }: Structure, element: ElementIndex) {
|
||||
for (let i = 0, _i = units.length; i < _i; i++) {
|
||||
if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
|
||||
|
||||
@@ -83,13 +83,16 @@ export const _atom_site: CifCategory<CifExportContext> = {
|
||||
}
|
||||
};
|
||||
|
||||
function prepostfixed(prefix: string | undefined, postfix: string | undefined, name: string) {
|
||||
if (prefix && postfix) return `${prefix}_${name}_${postfix}`;
|
||||
function prepostfixed(prefix: string | undefined, name: string) {
|
||||
if (prefix) return `${prefix}_${name}`;
|
||||
if (postfix) return `${name}_${postfix}`;
|
||||
return name;
|
||||
}
|
||||
|
||||
function prefixedInsCode(prefix: string | undefined) {
|
||||
if (!prefix) return 'pdbx_PDB_ins_code';
|
||||
return `pdbx_${prefix}_PDB_ins_code`;
|
||||
}
|
||||
|
||||
function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement.Location, prop: (e: StructureElement.Location) => any) {
|
||||
return (k: K, d: D) => prop(loc(k, d));
|
||||
}
|
||||
@@ -102,15 +105,14 @@ function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: (
|
||||
|
||||
export interface IdFieldsOptions {
|
||||
prefix?: string,
|
||||
postfix?: string,
|
||||
includeModelNum?: boolean
|
||||
}
|
||||
|
||||
export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifWriter.fields<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
.str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
encoder: E.deltaRLE,
|
||||
valueKind: (k, d) => {
|
||||
const e = getLocation(k, d);
|
||||
@@ -118,45 +120,45 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur
|
||||
return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
|
||||
}
|
||||
})
|
||||
.str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
.str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
.str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
}
|
||||
|
||||
export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifField.build<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
}
|
||||
|
||||
export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifField.build<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id));
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
}
|
||||
|
||||
export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
|
||||
const prefix = options && options.prefix, postfix = options && options.postfix;
|
||||
const prefix = options && options.prefix;
|
||||
const ret = CifWriter.fields<K, D>()
|
||||
.str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
.str(prepostfixed(prefix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
|
||||
.str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
|
||||
.int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
|
||||
encoder: E.deltaRLE,
|
||||
valueKind: (k, d) => {
|
||||
const e = getLocation(k, d);
|
||||
@@ -164,16 +166,16 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl
|
||||
return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
|
||||
}
|
||||
})
|
||||
.str(prepostfixed(prefix, postfix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id))
|
||||
.str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
.str(prepostfixed(prefix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id))
|
||||
.str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
|
||||
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
|
||||
|
||||
.str(prepostfixed(prefix, postfix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
|
||||
.str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
.str(prepostfixed(prefix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
|
||||
.str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
|
||||
.int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
|
||||
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
|
||||
|
||||
addModelNum(ret, getLocation, options);
|
||||
return ret.getFields();
|
||||
|
||||
@@ -132,7 +132,8 @@ function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifE
|
||||
type encode_mmCIF_categories_Params = {
|
||||
skipCategoryNames?: Set<string>,
|
||||
exportCtx?: CifExportContext,
|
||||
copyAllCategories?: boolean
|
||||
copyAllCategories?: boolean,
|
||||
customProperties?: CustomPropertyDescriptor[]
|
||||
}
|
||||
|
||||
/** Doesn't start a data block */
|
||||
@@ -144,7 +145,7 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
|
||||
const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
|
||||
|
||||
if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
|
||||
encode_mmCIF_categories_copyAll(encoder, ctx);
|
||||
encode_mmCIF_categories_copyAll(encoder, ctx, params);
|
||||
} else {
|
||||
encode_mmCIF_categories_default(encoder, ctx, params);
|
||||
}
|
||||
@@ -168,6 +169,14 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
|
||||
}
|
||||
}
|
||||
|
||||
if (params?.customProperties) {
|
||||
for (const customProp of params?.customProperties) {
|
||||
for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
encoder.writeCategory(cat, propCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
if (!s.hasCustomProperties) continue;
|
||||
for (const customProp of s.customPropertyDescriptors.all) {
|
||||
@@ -178,7 +187,7 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
|
||||
}
|
||||
}
|
||||
|
||||
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) {
|
||||
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) {
|
||||
const providedCategories = new Map<string, CifExportCategoryInfo>();
|
||||
|
||||
for (const cat of Categories) {
|
||||
@@ -188,12 +197,21 @@ function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExp
|
||||
const mapping = atom_site_operator_mapping(ctx);
|
||||
if (mapping) providedCategories.set(mapping[0].name, mapping);
|
||||
|
||||
const _params = params || { };
|
||||
for (const customProp of ctx.firstModel.customProperties.all) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx)) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
providedCategories.set(info[0].name, info);
|
||||
}
|
||||
}
|
||||
|
||||
if (params?.customProperties) {
|
||||
for (const customProp of params?.customProperties) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
providedCategories.set(info[0].name, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
if (!s.hasCustomProperties) continue;
|
||||
for (const customProp of s.customPropertyDescriptors.all) {
|
||||
|
||||
@@ -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>
|
||||
@@ -14,7 +14,8 @@ export type EntitySubtype = (
|
||||
mmCIF_Schema['entity_poly']['type']['T'] |
|
||||
mmCIF_Schema['pdbx_entity_branch']['type']['T'] |
|
||||
'ion' |
|
||||
'lipid'
|
||||
'lipid' |
|
||||
'peptide-like'
|
||||
)
|
||||
export const EntitySubtype = Column.Schema.Aliased<EntitySubtype>(Column.Schema.Str(''));
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// TODO add access to things like MOL2 charge ...
|
||||
80
src/mol-model/structure/model/properties/global-transform.ts
Normal file
80
src/mol-model/structure/model/properties/global-transform.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4, Tensor } from '../../../../mol-math/linear-algebra';
|
||||
import { FormatPropertyProvider } from '../../../../mol-model-formats/structure/common/property';
|
||||
import { CustomPropertyDescriptor } from '../../../custom-property';
|
||||
import { CifExportContext } from '../../structure';
|
||||
import { Model } from '../model';
|
||||
import { Column, Table } from '../../../../mol-data/db';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { toTable } from '../../../../mol-io/reader/cif/schema';
|
||||
|
||||
export namespace GlobalModelTransformInfo {
|
||||
const CategoryName = 'molstar_global_model_transform_info' as const;
|
||||
export const Schema = {
|
||||
[CategoryName]: {
|
||||
matrix: Column.Schema.Matrix(4, 4, Column.Schema.float)
|
||||
}
|
||||
};
|
||||
export type Schema = typeof Schema
|
||||
|
||||
export const Descriptor = CustomPropertyDescriptor({
|
||||
name: CategoryName,
|
||||
cifExport: {
|
||||
categories: [{
|
||||
name: CategoryName,
|
||||
instance(ctx: CifExportContext) {
|
||||
const mat = get(ctx.firstModel);
|
||||
if (!mat) return CifWriter.Category.Empty;
|
||||
const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: mat as unknown as Tensor.Data }]);
|
||||
return CifWriter.Category.ofTable(table);
|
||||
}
|
||||
}],
|
||||
prefix: 'molstar'
|
||||
}
|
||||
});
|
||||
|
||||
export const Provider = FormatPropertyProvider.create<Mat4>(Descriptor);
|
||||
|
||||
export function attach(model: Model, matrix: Mat4) {
|
||||
if (!model.customProperties.has(Descriptor)) {
|
||||
model.customProperties.add(Descriptor);
|
||||
}
|
||||
Provider.set(model, matrix);
|
||||
}
|
||||
|
||||
export function get(model: Model): Mat4 | undefined {
|
||||
return Provider.get(model);
|
||||
}
|
||||
|
||||
export function fromMmCif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
|
||||
const cat = model.sourceData.data.frame.categories[CategoryName];
|
||||
if (!cat) return;
|
||||
const table = toTable(Schema[CategoryName], cat);
|
||||
if (table._rowCount === 0) return;
|
||||
return table.matrix.value(0) as unknown as Mat4;
|
||||
}
|
||||
|
||||
export function hasData(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const cat = model.sourceData.data.frame.categories[CategoryName];
|
||||
return !!cat && cat.rowCount > 0;
|
||||
}
|
||||
|
||||
export function writeMmCif(encoder: CifWriter.Encoder, matrix: Mat4) {
|
||||
encoder.writeCategory({
|
||||
name: CategoryName,
|
||||
instance() {
|
||||
const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: matrix as unknown as Tensor.Data }]);
|
||||
return CifWriter.Category.ofTable(table);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -192,7 +192,7 @@ export const ProteinTerminusComponentTypeNames = new Set([
|
||||
'L-PEPTIDE NH3 AMINO TERMINUS', 'L-PEPTIDE COOH CARBOXY TERMINUS'
|
||||
]);
|
||||
|
||||
/** Chemical component type names for pepdite-like protein */
|
||||
/** Chemical component type names for peptide-like protein */
|
||||
export const OtherProteinComponentTypeNames = new Set([
|
||||
'PEPTIDE LINKING', 'PEPTIDE-LIKE',
|
||||
]);
|
||||
@@ -416,6 +416,8 @@ export function getEntitySubtype(compId: string, compType: string): EntitySubtyp
|
||||
return 'ion';
|
||||
} else if (LipidComponentTypeNames.has(compType) || LipidNames.has(compId)) {
|
||||
return 'lipid';
|
||||
} else if (OtherProteinComponentTypeNames.has(compType)) {
|
||||
return 'peptide-like';
|
||||
} else {
|
||||
return 'other';
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { UniqueArray } from '../../../../mol-data/generic';
|
||||
import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import Structure from '../structure';
|
||||
import Unit from '../unit';
|
||||
@@ -489,7 +489,7 @@ export namespace Loci {
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98');
|
||||
const tempPosBoundary = Vec3();
|
||||
export function getBoundary(loci: Loci): Boundary {
|
||||
export function getBoundary(loci: Loci, transform?: Mat4): Boundary {
|
||||
boundaryHelper.reset();
|
||||
|
||||
for (const e of loci.elements) {
|
||||
@@ -499,6 +499,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
|
||||
boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
@@ -510,6 +511,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
|
||||
boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class Structure {
|
||||
|
||||
/** Count of all bonds (intra- and inter-unit) in the structure */
|
||||
get bondCount() {
|
||||
if (!this._props.bondCount) {
|
||||
if (this._props.bondCount === -1) {
|
||||
this._props.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
|
||||
}
|
||||
return this._props.bondCount;
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace Bond {
|
||||
let count = 0;
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
const u = structure.units[i];
|
||||
if (Unit.isAtomic(u)) count += u.bonds.edgeCount / 2; // only count one direction
|
||||
if (Unit.isAtomic(u)) count += u.bonds.edgeCount;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
const structConn = unitA.model === unitB.model && StructConn.Provider.get(unitA.model);
|
||||
const indexPairs = unitA.model === unitB.model && IndexPairBonds.Provider.get(unitA.model);
|
||||
|
||||
const structConnExhaustive = unitA.model === unitB.model && StructConn.isExhaustive(unitA.model);
|
||||
|
||||
// the lookup queries need to happen in the "unitB space".
|
||||
// that means _imageA = inverseOperB(operA(aI))
|
||||
const imageTransform = Mat4.mul(_imageTransform, unitB.conformation.operator.inverse, unitA.conformation.operator.matrix);
|
||||
@@ -103,6 +105,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
// all are given and thus we don't need to compute any other
|
||||
if (added) continue;
|
||||
}
|
||||
if (structConnExhaustive) continue;
|
||||
|
||||
const occA = occupancyA.value(aI);
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
const structConn = StructConn.Provider.get(unit.model);
|
||||
const component = ComponentBond.Provider.get(unit.model);
|
||||
|
||||
const structConnExhaustive = StructConn.isExhaustive(unit.model);
|
||||
|
||||
const atomA: StructureElement.UnitIndex[] = [];
|
||||
const atomB: StructureElement.UnitIndex[] = [];
|
||||
const flags: number[] = [];
|
||||
@@ -135,6 +137,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
structConnAdded.add(_bI);
|
||||
}
|
||||
}
|
||||
if (structConnExhaustive) continue;
|
||||
|
||||
const raI = residueIndex[aI];
|
||||
const seqIdA = label_seq_id.value(raI);
|
||||
|
||||
@@ -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>
|
||||
@@ -20,6 +20,8 @@ import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynam
|
||||
import { createStructureColorThemeParams } from '../../helpers/structure-representation-params';
|
||||
import { ChainIdColorThemeProvider } from '../../../mol-theme/color/chain-id';
|
||||
import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operator-name';
|
||||
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
|
||||
import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
|
||||
|
||||
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
|
||||
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
|
||||
@@ -332,8 +334,12 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
structure.elementCount > 1000 &&
|
||||
structure.atomicResidueCount / structure.elementCount < 3;
|
||||
|
||||
const atomicType = lowResidueElementRatio ? 'spacefill' :
|
||||
highElementCount ? 'line' : 'ball-and-stick';
|
||||
const m = structure.models[0];
|
||||
const bondsGiven = !!IndexPairBonds.Provider.get(m) || StructConn.isExhaustive(m);
|
||||
|
||||
const atomicType = lowResidueElementRatio && !bondsGiven
|
||||
? 'spacefill' : highElementCount
|
||||
? 'line' : 'ball-and-stick';
|
||||
const showCarbohydrateSymbol = params.showCarbohydrateSymbol && !highElementCount && !lowResidueElementRatio;
|
||||
|
||||
if (showCarbohydrateSymbol) {
|
||||
@@ -343,7 +349,7 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
}
|
||||
|
||||
const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
const colorParams = lowResidueElementRatio
|
||||
const colorParams = lowResidueElementRatio && !bondsGiven
|
||||
? { carbonColor: { name: 'element-symbol', params: {} } }
|
||||
: ballAndStickColor;
|
||||
|
||||
|
||||
@@ -113,15 +113,6 @@ export const GroProvider: TrajectoryFormatProvider = {
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const Provider3dg: TrajectoryFormatProvider = {
|
||||
label: '3DG',
|
||||
description: '3DG',
|
||||
category: TrajectoryFormatCategory,
|
||||
stringExtensions: ['3dg'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFrom3DG),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const MolProvider: TrajectoryFormatProvider = {
|
||||
label: 'MOL/SDF',
|
||||
description: 'MOL/SDF',
|
||||
@@ -146,7 +137,6 @@ export const BuiltInTrajectoryFormats = [
|
||||
['pdb', PdbProvider] as const,
|
||||
['pdbqt', PdbqtProvider] as const,
|
||||
['gro', GroProvider] as const,
|
||||
['3dg', Provider3dg] as const,
|
||||
['mol', MolProvider] as const,
|
||||
['mol2', Mol2Provider] as const,
|
||||
] as const;
|
||||
|
||||
@@ -92,7 +92,7 @@ const polymer = StructureSelectionQuery('Polymer', MS.struct.modifier.union([
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide|nucleotide|peptide nucleic acid)', 'i'),
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide|peptide-like|nucleotide|peptide nucleic acid)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
])
|
||||
@@ -122,7 +122,7 @@ const trace = StructureSelectionQuery('Trace', MS.struct.modifier.union([
|
||||
const _proteinEntityTest = MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide|peptide-like)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]);
|
||||
|
||||
@@ -89,7 +89,8 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addDistance(a: StructureElement.Loci, b: StructureElement.Loci, options?: StructureMeasurementManagerAddOptions) {
|
||||
async addDistance(a: StructureElement.Loci, b: StructureElement.Loci,
|
||||
options?: StructureMeasurementManagerAddOptions & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsDistance3D>> }) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
const cellB = this.plugin.helpers.substructureParent.get(b.structure);
|
||||
|
||||
@@ -112,15 +113,17 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
customText: options?.customText || '',
|
||||
unitLabel: this.state.options.distanceUnitLabel,
|
||||
textColor: this.state.options.textColor,
|
||||
...options?.lineParams,
|
||||
...options?.labelParams
|
||||
...(options?.lineParams as any),
|
||||
...options?.labelParams,
|
||||
...options?.visualParams
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addAngle(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, options?: StructureMeasurementManagerAddOptions) {
|
||||
async addAngle(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci,
|
||||
options?: StructureMeasurementManagerAddOptions & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsAngle3D>> }) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
const cellB = this.plugin.helpers.substructureParent.get(b.structure);
|
||||
const cellC = this.plugin.helpers.substructureParent.get(c.structure);
|
||||
@@ -145,15 +148,17 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
.apply(StateTransforms.Representation.StructureSelectionsAngle3D, {
|
||||
customText: options?.customText || '',
|
||||
textColor: this.state.options.textColor,
|
||||
...options?.lineParams,
|
||||
...options?.labelParams
|
||||
...(options?.lineParams as any),
|
||||
...options?.labelParams,
|
||||
...options?.visualParams
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addDihedral(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, d: StructureElement.Loci, options?: StructureMeasurementManagerAddOptions) {
|
||||
async addDihedral(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, d: StructureElement.Loci,
|
||||
options?: StructureMeasurementManagerAddOptions & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsDihedral3D>> }) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
const cellB = this.plugin.helpers.substructureParent.get(b.structure);
|
||||
const cellC = this.plugin.helpers.substructureParent.get(c.structure);
|
||||
@@ -181,15 +186,17 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
.apply(StateTransforms.Representation.StructureSelectionsDihedral3D, {
|
||||
customText: options?.customText || '',
|
||||
textColor: this.state.options.textColor,
|
||||
...options?.lineParams,
|
||||
...options?.labelParams
|
||||
...(options?.lineParams as any),
|
||||
...options?.labelParams,
|
||||
...options?.visualParams
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addLabel(a: StructureElement.Loci, options?: Omit<StructureMeasurementManagerAddOptions, 'customText' | 'lineParams'>) {
|
||||
async addLabel(a: StructureElement.Loci,
|
||||
options?: Omit<StructureMeasurementManagerAddOptions, 'customText' | 'lineParams'> & { visualParams?: Partial<StateTransformer.Params<typeof StateTransforms.Representation.StructureSelectionsLabel3D>> }) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
|
||||
if (!cellA) return;
|
||||
@@ -207,7 +214,8 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
}, { dependsOn, tags: options?.selectionTags })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
|
||||
textColor: this.state.options.textColor,
|
||||
...options?.labelParams
|
||||
...options?.labelParams,
|
||||
...options?.visualParams
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { File3DG } from '../mol-io/reader/3dg/parser';
|
||||
import { Ccp4File } from '../mol-io/reader/ccp4/schema';
|
||||
import { CifFile } from '../mol-io/reader/cif';
|
||||
import { DcdFile } from '../mol-io/reader/dcd/parser';
|
||||
@@ -83,7 +82,6 @@ export namespace PluginStateObject {
|
||||
{ kind: 'cif', data: CifFile } |
|
||||
{ kind: 'pdb', data: CifFile } |
|
||||
{ kind: 'gro', data: CifFile } |
|
||||
{ kind: '3dg', data: File3DG } |
|
||||
{ kind: 'dcd', data: DcdFile } |
|
||||
{ kind: 'ccp4', data: Ccp4File } |
|
||||
{ kind: 'dsn6', data: Dsn6File } |
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { parse3DG } from '../../mol-io/reader/3dg/parser';
|
||||
import { parseDcd } from '../../mol-io/reader/dcd/parser';
|
||||
import { parseGRO } from '../../mol-io/reader/gro/parser';
|
||||
import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
|
||||
import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
|
||||
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
|
||||
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
|
||||
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
@@ -52,7 +50,6 @@ export { TrajectoryFromMOL };
|
||||
export { TrajectoryFromMOL2 };
|
||||
export { TrajectoryFromCube };
|
||||
export { TrajectoryFromCifCore };
|
||||
export { TrajectoryFrom3DG };
|
||||
export { ModelFromTrajectory };
|
||||
export { StructureFromTrajectory };
|
||||
export { StructureFromModel };
|
||||
@@ -339,24 +336,6 @@ const TrajectoryFromCifCore = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFrom3DG = typeof TrajectoryFrom3DG
|
||||
const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-3dg',
|
||||
display: { name: 'Parse 3DG', description: 'Parse 3DG string and create trajectory.' },
|
||||
from: [SO.Data.String],
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse 3DG', async ctx => {
|
||||
const parsed = await parse3DG(a.data).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
const models = await trajectoryFrom3DG(parsed.result).runInContext(ctx);
|
||||
const props = trajectoryProps(models);
|
||||
return new SO.Molecule.Trajectory(models, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
|
||||
type ModelFromTrajectory = typeof ModelFromTrajectory
|
||||
const ModelFromTrajectory = PluginStateTransform.BuiltIn({
|
||||
|
||||
@@ -131,7 +131,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params);
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params));
|
||||
|
||||
// TODO set initial state, repr.setState({})
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
|
||||
@@ -205,6 +204,8 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
unwindStructureAssembly(structure, unitTransforms, newParams.t);
|
||||
@@ -240,6 +241,8 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.source.data;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.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);
|
||||
@@ -287,6 +290,8 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -337,6 +342,8 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -383,6 +390,8 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofScript(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -430,6 +439,8 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -476,6 +487,8 @@ const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofScript(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -523,6 +536,8 @@ const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofBundle(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
@@ -619,7 +634,6 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
// TODO: allow for small molecules
|
||||
return oldParams.type.name === newParams.type.name;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
@@ -627,10 +641,10 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
const provider = plugin.representation.volume.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
const props = params.type.params || {};
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
|
||||
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
|
||||
// TODO set initial state, repr.setState({})
|
||||
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
});
|
||||
@@ -673,7 +687,6 @@ const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return Task.create('Shape Representation', async ctx => {
|
||||
const props = { ...PD.getDefaultValues(a.data.params), ...params };
|
||||
const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils);
|
||||
// TODO set initial state, repr.setState({})
|
||||
await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
|
||||
});
|
||||
|
||||
@@ -186,6 +186,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
|
||||
case 'file-list': return FileListControl;
|
||||
case 'select': return SelectControl;
|
||||
case 'value-ref': return ValueRefControl;
|
||||
case 'data-ref': return void 0;
|
||||
case 'text': return TextControl;
|
||||
case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined'
|
||||
? BoundedIntervalControl : IntervalControl;
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ElementSequenceWrapper } from './sequence/element';
|
||||
import { elementLabel } from '../mol-theme/label';
|
||||
import { Icon, HelpOutlineSvg } from './controls/icons';
|
||||
import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
|
||||
import { arrayEqual } from '../mol-util/array';
|
||||
|
||||
const MaxDisplaySequenceLength = 5000;
|
||||
|
||||
@@ -34,14 +35,14 @@ function opKey(l: StructureElement.Location) {
|
||||
}
|
||||
|
||||
function splitModelEntityId(modelEntityId: string) {
|
||||
const [ modelIdx, entityId ] = modelEntityId.split('|');
|
||||
return [ parseInt(modelIdx), entityId ];
|
||||
const [modelIdx, entityId] = modelEntityId.split('|');
|
||||
return [parseInt(modelIdx), entityId];
|
||||
}
|
||||
|
||||
function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureSelectionManager): SequenceWrapper.Any | string {
|
||||
function getSequenceWrapper(state: { structure: Structure, modelEntityId: string, chainGroupId: number, operatorKey: string }, structureSelection: StructureSelectionManager): SequenceWrapper.Any | string {
|
||||
const { structure, modelEntityId, chainGroupId, operatorKey } = state;
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
|
||||
const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
|
||||
|
||||
const units: Unit[] = [];
|
||||
|
||||
@@ -93,7 +94,7 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct
|
||||
}
|
||||
}
|
||||
|
||||
function getModelEntityOptions(structure: Structure) {
|
||||
function getModelEntityOptions(structure: Structure, polymersOnly = false) {
|
||||
const options: [string, string][] = [];
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const seen = new Set<string>();
|
||||
@@ -104,17 +105,18 @@ function getModelEntityOptions(structure: Structure) {
|
||||
const modelIdx = structure.getModelIndex(unit.model);
|
||||
const key = `${modelIdx}|${id}`;
|
||||
if (seen.has(key)) continue;
|
||||
if (polymersOnly && SP.entity.type(l) !== 'polymer') continue;
|
||||
|
||||
let description = SP.entity.pdbx_description(l).join(', ');
|
||||
if (structure.models.length) {
|
||||
if (structure.representativeModel) { // indicates model trajectory
|
||||
description += ` (Model ${structure.models[modelIdx].modelNum})`;
|
||||
} else if (description.startsWith('Polymer ')) { // indicates generic entity name
|
||||
} else if (description.startsWith('Polymer ')) { // indicates generic entity name
|
||||
description += ` (${structure.models[modelIdx].entry})`;
|
||||
}
|
||||
}
|
||||
const label = `${id}: ${description}`;
|
||||
options.push([ key, label ]);
|
||||
options.push([key, label]);
|
||||
seen.add(key);
|
||||
}
|
||||
|
||||
@@ -126,7 +128,7 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
|
||||
const options: [number, string][] = [];
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const seen = new Set<number>();
|
||||
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
|
||||
const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
|
||||
|
||||
for (const unit of structure.units) {
|
||||
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
|
||||
@@ -140,7 +142,7 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
|
||||
// - more than one chain in a unit
|
||||
let label = elementLabel(l, { granularity: 'chain', hidePrefix: true, htmlStyling: false });
|
||||
|
||||
options.push([ id, label ]);
|
||||
options.push([id, label]);
|
||||
seen.add(id);
|
||||
}
|
||||
|
||||
@@ -152,7 +154,7 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
|
||||
const options: [string, string][] = [];
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const seen = new Set<string>();
|
||||
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
|
||||
const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
|
||||
|
||||
for (const unit of structure.units) {
|
||||
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
|
||||
@@ -164,7 +166,7 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
|
||||
if (seen.has(id)) continue;
|
||||
|
||||
const label = unit.conformation.operator.name;
|
||||
options.push([ id, label ]);
|
||||
options.push([id, label]);
|
||||
seen.add(id);
|
||||
}
|
||||
|
||||
@@ -174,49 +176,64 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
|
||||
|
||||
function getStructureOptions(state: State) {
|
||||
const options: [string, string][] = [];
|
||||
const all: Structure[] = [];
|
||||
|
||||
const structures = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
|
||||
for (const s of structures) {
|
||||
if (!s.obj?.data) continue;
|
||||
|
||||
all.push(s.obj.data);
|
||||
options.push([s.transform.ref, s.obj!.data.label]);
|
||||
}
|
||||
|
||||
if (options.length === 0) options.push(['', 'No structure']);
|
||||
return options;
|
||||
return { options, all };
|
||||
}
|
||||
|
||||
export type SequenceViewMode = 'single' | 'polymers' | 'all'
|
||||
const SequenceViewModeParam = PD.Select<SequenceViewMode>('single', [['single', 'Chain'], ['polymers', 'Polymers'], ['all', 'Everything']]);
|
||||
|
||||
type SequenceViewState = {
|
||||
structureOptions: { options: [string, string][], all: Structure[] },
|
||||
structure: Structure,
|
||||
structureRef: string,
|
||||
modelEntityId: string,
|
||||
chainGroupId: number,
|
||||
operatorKey: string
|
||||
operatorKey: string,
|
||||
mode: SequenceViewMode
|
||||
}
|
||||
|
||||
export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
state = { structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '' }
|
||||
export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceViewMode }, SequenceViewState> {
|
||||
state: SequenceViewState = { structureOptions: { options: [], all: [] }, structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '', mode: 'single' }
|
||||
|
||||
componentDidMount() {
|
||||
if (this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)).length > 0) this.setState(this.getInitialState());
|
||||
|
||||
this.subscribe(this.plugin.state.events.object.updated, ({ ref, obj }) => {
|
||||
if (ref === this.state.structureRef && obj && obj.type === PSO.Molecule.Structure.type && obj.data !== this.state.structure) {
|
||||
this.setState(this.getInitialState());
|
||||
this.sync();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.object.created, ({ obj }) => {
|
||||
if (obj && obj.type === PSO.Molecule.Structure.type) {
|
||||
this.setState(this.getInitialState());
|
||||
this.sync();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.object.removed, ({ obj }) => {
|
||||
if (obj && obj.type === PSO.Molecule.Structure.type && obj.data === this.state.structure) {
|
||||
this.setState(this.getInitialState());
|
||||
this.sync();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sync() {
|
||||
const structureOptions = getStructureOptions(this.plugin.state.data);
|
||||
if (arrayEqual(structureOptions.all, this.state.structureOptions.all)) return;
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
|
||||
private getStructure(ref: string) {
|
||||
const state = this.plugin.state.data;
|
||||
const cell = state.select(ref)[0];
|
||||
@@ -224,12 +241,40 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
return (cell.obj as PSO.Molecule.Structure).data;
|
||||
}
|
||||
|
||||
private getSequenceWrapper() {
|
||||
return getSequenceWrapper(this.state, this.plugin.managers.structure.selection);
|
||||
private getSequenceWrapper(params: SequenceView['params']) {
|
||||
return {
|
||||
wrapper: getSequenceWrapper(this.state, this.plugin.managers.structure.selection),
|
||||
label: `${PD.optionLabel(params.chain, this.state.chainGroupId)} | ${PD.optionLabel(params.entity, this.state.modelEntityId)}`
|
||||
};
|
||||
}
|
||||
|
||||
private getSequenceWrappers(params: SequenceView['params']) {
|
||||
if (this.state.mode === 'single') return [this.getSequenceWrapper(params)];
|
||||
|
||||
const structure = this.getStructure(this.state.structureRef);
|
||||
const wrappers: { wrapper: (string | SequenceWrapper.Any), label: string }[] = [];
|
||||
|
||||
for (const [modelEntityId, eLabel] of getModelEntityOptions(structure, this.state.mode === 'polymers')) {
|
||||
for (const [chainGroupId, cLabel] of getChainOptions(structure, modelEntityId)) {
|
||||
for (const [operatorKey] of getOperatorOptions(structure, modelEntityId, chainGroupId)) {
|
||||
wrappers.push({
|
||||
wrapper: getSequenceWrapper({
|
||||
structure,
|
||||
modelEntityId,
|
||||
chainGroupId,
|
||||
operatorKey
|
||||
}, this.plugin.managers.structure.selection),
|
||||
label: `${cLabel} | ${eLabel}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return wrappers;
|
||||
}
|
||||
|
||||
private getInitialState(): SequenceViewState {
|
||||
const structureRef = getStructureOptions(this.plugin.state.data)[0][0];
|
||||
const structureOptions = getStructureOptions(this.plugin.state.data);
|
||||
const structureRef = structureOptions.options[0][0];
|
||||
const structure = this.getStructure(structureRef);
|
||||
let modelEntityId = getModelEntityOptions(structure)[0][0];
|
||||
let chainGroupId = getChainOptions(structure, modelEntityId)[0][0];
|
||||
@@ -239,20 +284,20 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
chainGroupId = this.state.chainGroupId;
|
||||
operatorKey = this.state.operatorKey;
|
||||
}
|
||||
return { structure, structureRef, modelEntityId, chainGroupId, operatorKey };
|
||||
return { structureOptions, structure, structureRef, modelEntityId, chainGroupId, operatorKey, mode: this.props.defaultMode ?? 'single' };
|
||||
}
|
||||
|
||||
private get params() {
|
||||
const { structure, modelEntityId, chainGroupId } = this.state;
|
||||
const structureOptions = getStructureOptions(this.plugin.state.data);
|
||||
const { structureOptions, structure, modelEntityId, chainGroupId } = this.state;
|
||||
const entityOptions = getModelEntityOptions(structure);
|
||||
const chainOptions = getChainOptions(structure, modelEntityId);
|
||||
const operatorOptions = getOperatorOptions(structure, modelEntityId, chainGroupId);
|
||||
return {
|
||||
structure: PD.Select(structureOptions[0][0], structureOptions, { shortLabel: true }),
|
||||
structure: PD.Select(structureOptions.options[0][0], structureOptions.options, { shortLabel: true }),
|
||||
entity: PD.Select(entityOptions[0][0], entityOptions, { shortLabel: true }),
|
||||
chain: PD.Select(chainOptions[0][0], chainOptions, { shortLabel: true, twoColumns: true, label: 'Chain' }),
|
||||
operator: PD.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true })
|
||||
operator: PD.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true }),
|
||||
mode: SequenceViewModeParam
|
||||
};
|
||||
}
|
||||
|
||||
@@ -261,16 +306,24 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
structure: this.state.structureRef,
|
||||
entity: this.state.modelEntityId,
|
||||
chain: this.state.chainGroupId,
|
||||
operator: this.state.operatorKey
|
||||
operator: this.state.operatorKey,
|
||||
mode: this.state.mode
|
||||
};
|
||||
}
|
||||
|
||||
private setParamProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
const state = { ...this.state };
|
||||
switch (p.name) {
|
||||
case 'mode':
|
||||
state.mode = p.value;
|
||||
if (this.state.mode === state.mode) return;
|
||||
|
||||
if (state.mode === 'all' || state.mode === 'polymers') {
|
||||
break;
|
||||
}
|
||||
case 'structure':
|
||||
state.structureRef = p.value;
|
||||
state.structure = this.getStructure(p.value);
|
||||
if (p.name === 'structure') state.structureRef = p.value;
|
||||
state.structure = this.getStructure(state.structureRef);
|
||||
state.modelEntityId = getModelEntityOptions(state.structure)[0][0];
|
||||
state.chainGroupId = getChainOptions(state.structure, state.modelEntityId)[0][0];
|
||||
state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0];
|
||||
@@ -296,17 +349,16 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
return <div className='msp-sequence'>
|
||||
<div className='msp-sequence-select'>
|
||||
<Icon svg={HelpOutlineSvg} style={{ cursor: 'help', position: 'absolute', right: 0, top: 0 }}
|
||||
title='This shows a single sequence. Use the controls to show a different sequence.'/>
|
||||
title='Shows a sequence of one or more chains. Use the controls to alter selection.' />
|
||||
|
||||
<span>Sequence</span><span style={{ fontWeight: 'normal' }}>No structure available</span>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const sequenceWrapper = this.getSequenceWrapper();
|
||||
|
||||
const params = this.params;
|
||||
const values = this.values;
|
||||
const sequenceWrappers = this.getSequenceWrappers(params);
|
||||
|
||||
return <div className='msp-sequence'>
|
||||
<div className='msp-sequence-select'>
|
||||
@@ -315,16 +367,34 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
|
||||
|
||||
<span>Sequence of</span>
|
||||
<PureSelectControl title={`[Structure] ${PD.optionLabel(params.structure, values.structure)}`} param={params.structure} name='structure' value={values.structure} onChange={this.setParamProps} />
|
||||
<PureSelectControl title={`[Entity] ${PD.optionLabel(params.entity, values.entity)}`} param={params.entity} name='entity' value={values.entity} onChange={this.setParamProps} />
|
||||
<PureSelectControl title={`[Chain] ${PD.optionLabel(params.chain, values.chain)}`} param={params.chain} name='chain' value={values.chain} onChange={this.setParamProps} />
|
||||
<PureSelectControl title={`[Mode]`} param={SequenceViewModeParam} name='mode' value={values.mode} onChange={this.setParamProps} />
|
||||
{values.mode === 'single' && <PureSelectControl title={`[Entity] ${PD.optionLabel(params.entity, values.entity)}`} param={params.entity} name='entity' value={values.entity} onChange={this.setParamProps} />}
|
||||
{values.mode === 'single' && <PureSelectControl title={`[Chain] ${PD.optionLabel(params.chain, values.chain)}`} param={params.chain} name='chain' value={values.chain} onChange={this.setParamProps} />}
|
||||
{params.operator.options.length > 1 && <>
|
||||
<PureSelectControl title={`[Instance] ${PD.optionLabel(params.operator, values.operator)}`} param={params.operator} name='operator' value={values.operator} onChange={this.setParamProps} />
|
||||
</>}
|
||||
</div>
|
||||
|
||||
{typeof sequenceWrapper === 'string'
|
||||
? <div className='msp-sequence-wrapper msp-sequence-wrapper-non-empty'>{sequenceWrapper}</div>
|
||||
: <Sequence sequenceWrapper={sequenceWrapper} />}
|
||||
<NonEmptySequenceWrapper>
|
||||
{sequenceWrappers.map((s, i) => {
|
||||
const elem = typeof s.wrapper === 'string'
|
||||
? <div key={i} className='msp-sequence-wrapper'>{s.wrapper}</div>
|
||||
: <Sequence key={i} sequenceWrapper={s.wrapper} />;
|
||||
|
||||
if (values.mode === 'single') return elem;
|
||||
|
||||
return <>
|
||||
<div className='msp-sequence-chain-label'>{s.label}</div>
|
||||
{elem}
|
||||
</>;
|
||||
})}
|
||||
</NonEmptySequenceWrapper>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function NonEmptySequenceWrapper({ children }: { children: React.ReactNode }) {
|
||||
return <div className='msp-sequence-wrapper-non-empty'>
|
||||
{children}
|
||||
</div>;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import { Representation } from '../../mol-repr/representation';
|
||||
type SequenceProps = {
|
||||
sequenceWrapper: SequenceWrapper.Any,
|
||||
sequenceNumberPeriod?: number,
|
||||
hideSequenceNumbers?: boolean
|
||||
hideSequenceNumbers?: boolean,
|
||||
}
|
||||
|
||||
/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
|
||||
@@ -292,7 +292,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
this.updateMarker();
|
||||
|
||||
return <div
|
||||
className='msp-sequence-wrapper msp-sequence-wrapper-non-empty'
|
||||
className='msp-sequence-wrapper'
|
||||
onContextMenu={this.contextMenu}
|
||||
onMouseDown={this.mouseDown}
|
||||
onMouseUp={this.mouseUp}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
left: 18px;
|
||||
bottom: 0;
|
||||
right: 62px;
|
||||
display: grid;
|
||||
display: flex;
|
||||
}
|
||||
> div:last-child {
|
||||
position: absolute;
|
||||
@@ -137,7 +137,7 @@
|
||||
left: 35px;
|
||||
bottom: 0;
|
||||
right: 37px;
|
||||
display: grid;
|
||||
display: flex;
|
||||
}
|
||||
> div:last-child {
|
||||
position: absolute;
|
||||
|
||||
@@ -609,4 +609,9 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msp-list-unstyled {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
@@ -43,14 +43,6 @@ $sequence-select-height: 24px;
|
||||
// use $control-spacing for top to have space for sequence numebrs
|
||||
padding: $control-spacing $control-spacing $info-vertical-padding $control-spacing;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.msp-sequence-wrapper-non-empty {
|
||||
@@ -58,7 +50,25 @@ $sequence-select-height: 24px;
|
||||
line-height: 180%;
|
||||
font-family: "Courier New", monospace;
|
||||
background: $msp-form-control-background;
|
||||
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: absolute;
|
||||
top: $sequence-select-height + 1px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.msp-sequence-chain-label {
|
||||
margin-left: $control-spacing;
|
||||
margin-top: $control-spacing;
|
||||
user-select: none;
|
||||
color: $sequence-number-color;
|
||||
font-size: 90%;
|
||||
line-height: 90%;
|
||||
padding-left: 0.2em;
|
||||
}
|
||||
|
||||
.msp-sequence-wrapper {
|
||||
@@ -74,6 +84,14 @@ $sequence-select-height: 24px;
|
||||
margin: 0em 0.2em 0em 0em;
|
||||
}
|
||||
|
||||
.msp-sequence-label {
|
||||
color: $sequence-number-color;
|
||||
font-size: 90%;
|
||||
line-height: 90%;
|
||||
padding-bottom: 1em;
|
||||
padding-left: 0.2em;
|
||||
}
|
||||
|
||||
.msp-sequence-number {
|
||||
color: $sequence-number-color;
|
||||
word-break: keep-all;
|
||||
|
||||
@@ -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>
|
||||
@@ -7,11 +7,6 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { resizeCanvas } from '../../mol-canvas3d/util';
|
||||
import { Subject } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/internal/operators/debounceTime';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { Color } from '../../mol-util/color';
|
||||
|
||||
interface ViewportCanvasState {
|
||||
noWebGl: boolean
|
||||
@@ -41,39 +36,13 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
|
||||
this.setState({ showLogo: !this.plugin.canvas3d?.reprCount.value });
|
||||
}
|
||||
|
||||
private handleResize = () => {
|
||||
const container = this.container.current;
|
||||
const canvas = this.canvas.current;
|
||||
if (container && canvas) {
|
||||
const pixelScale = this.plugin.config.get(PluginConfig.General.PixelScale) || 1;
|
||||
resizeCanvas(canvas, container, pixelScale);
|
||||
const [r, g, b] = Color.toRgbNormalized(this.plugin.canvas3d!.props.renderer.backgroundColor);
|
||||
const a = this.plugin.canvas3d!.props.transparentBackground ? 0 : 1;
|
||||
this.plugin.canvas3d!.webgl.clear(r, g, b, a);
|
||||
this.plugin.canvas3d!.handleResize();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) {
|
||||
this.setState({ noWebGl: true });
|
||||
return;
|
||||
}
|
||||
this.handleLogo();
|
||||
this.handleResize();
|
||||
|
||||
const canvas3d = this.plugin.canvas3d!;
|
||||
this.subscribe(canvas3d.reprCount, this.handleLogo);
|
||||
|
||||
const resized = new Subject();
|
||||
const resize = () => resized.next();
|
||||
|
||||
this.subscribe(resized.pipe(debounceTime(1000 / 24)), () => this.handleResize());
|
||||
this.subscribe(canvas3d.input.resize, resize);
|
||||
this.subscribe(canvas3d.interaction.click, e => this.plugin.behaviors.interaction.click.next(e));
|
||||
this.subscribe(canvas3d.interaction.drag, e => this.plugin.behaviors.interaction.drag.next(e));
|
||||
this.subscribe(canvas3d.interaction.hover, e => this.plugin.behaviors.interaction.hover.next(e));
|
||||
this.subscribe(this.plugin.layout.events.updated, resize);
|
||||
this.subscribe(this.plugin.canvas3d!.reprCount, this.handleLogo);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
@@ -44,7 +44,7 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action);
|
||||
this.ctx.canvas3d.mark(interactionLoci, action);
|
||||
}
|
||||
register() {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
|
||||
@@ -57,12 +57,14 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
let matched = false;
|
||||
|
||||
if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly(current);
|
||||
// remove repr to highlight loci everywhere on hover
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: current.loci });
|
||||
matched = true;
|
||||
}
|
||||
|
||||
if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend(current);
|
||||
// remove repr to highlight loci everywhere on hover
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci: current.loci });
|
||||
matched = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { Volume, Grid } from '../../../../mol-model/volume';
|
||||
import { VolumeServerHeader, VolumeServerInfo } from './model';
|
||||
import { Box3D } from '../../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { PluginBehavior } from '../../behavior';
|
||||
import { LRUCache } from '../../../../mol-util/lru-cache';
|
||||
@@ -23,6 +23,7 @@ import { StructureElement, Structure } from '../../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../context';
|
||||
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
|
||||
|
||||
@@ -302,6 +303,7 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
}
|
||||
|
||||
private _invTransform: Mat4 = Mat4();
|
||||
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
|
||||
if (Loci.isEmpty(loci)) {
|
||||
return Box3D();
|
||||
@@ -312,11 +314,16 @@ export namespace VolumeStreaming {
|
||||
const root = this.getStructureRoot();
|
||||
if (!root || root.obj?.data !== parent.obj?.data) return Box3D();
|
||||
|
||||
const transform = GlobalModelTransformInfo.get(root.obj?.data.models[0]!);
|
||||
if (transform) Mat4.invert(this._invTransform, transform);
|
||||
|
||||
const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci);
|
||||
const box = StructureElement.Loci.getBoundary(extendedLoci).box;
|
||||
const box = StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box;
|
||||
|
||||
if (StructureElement.Loci.size(extendedLoci) === 1) {
|
||||
Box3D.expand(box, box, Vec3.create(1, 1, 1));
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
@@ -360,7 +367,6 @@ export namespace VolumeStreaming {
|
||||
const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box';
|
||||
|
||||
this.params = params;
|
||||
|
||||
let box: Box3D | undefined = void 0, emptyData = false;
|
||||
|
||||
switch (params.entry.params.view.name) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Box3D } from '../../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { PluginConfig } from '../../../config';
|
||||
import { Model } from '../../../../mol-model/structure';
|
||||
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
|
||||
|
||||
function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
|
||||
entries.push({
|
||||
@@ -253,20 +254,22 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
|
||||
channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true })
|
||||
}
|
||||
})({
|
||||
apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
apply: ({ a, params: srcParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
const channel = a.data.channels[srcParams.channel];
|
||||
if (!channel) return StateObject.Null;
|
||||
|
||||
const params = createVolumeProps(a.data, srcParams.channel);
|
||||
|
||||
const provider = VolumeRepresentationRegistry.BuiltIn.isosurface;
|
||||
const props = params.type.params || {};
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
|
||||
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
|
||||
const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
|
||||
const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
|
||||
await repr.createOrUpdate(props, channel.data).runInContext(ctx);
|
||||
if (transform) repr.setState({ transform });
|
||||
return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
|
||||
}),
|
||||
update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
|
||||
// TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
|
||||
|
||||
const channel = a.data.channels[newParams.channel];
|
||||
@@ -277,6 +280,13 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...params.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
|
||||
await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
|
||||
|
||||
// TODO: set the transform here as well in case the structure moves?
|
||||
// doing this here now breaks the code for some reason...
|
||||
// const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
|
||||
// const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
|
||||
// if (transform) b.data.repr.setState({ transform });
|
||||
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
@@ -24,8 +24,10 @@ export const PluginConfig = {
|
||||
General: {
|
||||
IsBusyTimeoutMs: item('plugin-config.is-busy-timeout', 750),
|
||||
DisableAntialiasing: item('plugin-config.disable-antialiasing', false),
|
||||
DisablePreserveDrawingBuffer: item('plugin-config.disable-preserve-drawing-buffer', false),
|
||||
PixelScale: item('plugin-config.pixel-scale', 1),
|
||||
EnableWboit: item('plugin-config.enable-wboit', false)
|
||||
PickScale: item('plugin-config.pick-scale', 0.25),
|
||||
EnableWboit: item('plugin-config.enable-wboit', false),
|
||||
},
|
||||
State: {
|
||||
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),
|
||||
|
||||
@@ -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>
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
import produce, { setAutoFreeze } from 'immer';
|
||||
import { List } from 'immutable';
|
||||
import { merge } from 'rxjs';
|
||||
import { Canvas3D, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d';
|
||||
import { merge, Subscription } from 'rxjs';
|
||||
import { Canvas3D, Canvas3DContext, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d';
|
||||
import { CustomProperty } from '../mol-model-props/common/custom-property';
|
||||
import { Model, Structure } from '../mol-model/structure';
|
||||
import { DataBuilder } from '../mol-plugin-state/builder/data';
|
||||
@@ -61,6 +61,7 @@ import { VolumeHierarchyManager } from '../mol-plugin-state/manager/volume/hiera
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
import { Vec2 } from '../mol-math/linear-algebra';
|
||||
import { PluginAnimationLoop } from './animation-loop';
|
||||
import { resizeCanvas } from '../mol-canvas3d/util';
|
||||
|
||||
export class PluginContext {
|
||||
runTask = <T>(task: Task<T>, params?: { useOverlay?: boolean }) => this.managers.task.run(task, params);
|
||||
@@ -70,6 +71,8 @@ export class PluginContext {
|
||||
return object;
|
||||
}
|
||||
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
private disposed = false;
|
||||
private ev = RxEventHelper.create();
|
||||
|
||||
@@ -104,6 +107,7 @@ export class PluginContext {
|
||||
}
|
||||
} as const;
|
||||
|
||||
readonly canvas3dContext: Canvas3DContext | undefined;
|
||||
readonly canvas3d: Canvas3D | undefined;
|
||||
readonly animationLoop = new PluginAnimationLoop(this);
|
||||
readonly layout = new PluginLayout(this);
|
||||
@@ -189,9 +193,12 @@ export class PluginContext {
|
||||
if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
|
||||
|
||||
const antialias = !(this.config.get(PluginConfig.General.DisableAntialiasing) ?? false);
|
||||
const preserveDrawingBuffer = !(this.config.get(PluginConfig.General.DisablePreserveDrawingBuffer) ?? false);
|
||||
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
|
||||
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
|
||||
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas, {}, { antialias, pixelScale, enableWboit });
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
|
||||
this.canvas3dInit.next(true);
|
||||
let props = this.spec.components?.viewport?.canvas3d;
|
||||
|
||||
@@ -209,6 +216,15 @@ export class PluginContext {
|
||||
}
|
||||
this.animationLoop.start();
|
||||
(this.helpers.viewportScreenshot as ViewportScreenshotHelper) = new ViewportScreenshotHelper(this);
|
||||
|
||||
this.subs.push(this.canvas3d!.interaction.click.subscribe(e => this.behaviors.interaction.click.next(e)));
|
||||
this.subs.push(this.canvas3d!.interaction.drag.subscribe(e => this.behaviors.interaction.drag.next(e)));
|
||||
this.subs.push(this.canvas3d!.interaction.hover.subscribe(e => this.behaviors.interaction.hover.next(e)));
|
||||
this.subs.push(this.canvas3d!.input.resize.subscribe(() => this.handleResize()));
|
||||
this.subs.push(this.layout.events.updated.subscribe(() => requestAnimationFrame(() => this.handleResize())));
|
||||
|
||||
this.handleResize();
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.log.error('' + e);
|
||||
@@ -217,6 +233,16 @@ export class PluginContext {
|
||||
}
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
const canvas = this.canvas3dContext?.canvas;
|
||||
const container = this.layout.root;
|
||||
if (container && canvas) {
|
||||
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
|
||||
resizeCanvas(canvas, container, pixelScale);
|
||||
this.canvas3d?.requestResize();
|
||||
}
|
||||
}
|
||||
|
||||
readonly log = {
|
||||
entries: List<LogEntry>(),
|
||||
entry: (e: LogEntry) => this.events.log.next(e),
|
||||
@@ -254,10 +280,17 @@ export class PluginContext {
|
||||
return PluginCommands.State.RemoveObject(this, { state: this.state.data, ref: StateTransform.RootRef });
|
||||
}
|
||||
|
||||
dispose() {
|
||||
dispose(options?: { doNotForceWebGLContextLoss?: boolean }) {
|
||||
if (this.disposed) return;
|
||||
|
||||
for (const s of this.subs) {
|
||||
s.unsubscribe();
|
||||
}
|
||||
this.subs = [];
|
||||
|
||||
this.commands.dispose();
|
||||
this.canvas3d?.dispose();
|
||||
this.canvas3dContext?.dispose(options);
|
||||
this.ev.dispose();
|
||||
this.state.dispose();
|
||||
this.managers.task.dispose();
|
||||
@@ -271,9 +304,9 @@ export class PluginContext {
|
||||
}
|
||||
|
||||
private initBehaviorEvents() {
|
||||
merge(this.state.data.behaviors.isUpdating, this.state.behaviors.behaviors.isUpdating).subscribe(u => {
|
||||
this.subs.push(merge(this.state.data.behaviors.isUpdating, this.state.behaviors.behaviors.isUpdating).subscribe(u => {
|
||||
if (this.behaviors.state.isUpdating.value !== u) this.behaviors.state.isUpdating.next(u);
|
||||
});
|
||||
}));
|
||||
|
||||
const timeoutMs = this.config.get(PluginConfig.General.IsBusyTimeoutMs) || 750;
|
||||
const isBusy = this.behaviors.state.isBusy;
|
||||
@@ -287,7 +320,7 @@ export class PluginContext {
|
||||
timeout = void 0;
|
||||
};
|
||||
|
||||
merge(this.behaviors.state.isUpdating, this.behaviors.state.isAnimating).subscribe(v => {
|
||||
this.subs.push(merge(this.behaviors.state.isUpdating, this.behaviors.state.isAnimating).subscribe(v => {
|
||||
const isUpdating = this.behaviors.state.isUpdating.value;
|
||||
const isAnimating = this.behaviors.state.isAnimating.value;
|
||||
|
||||
@@ -300,13 +333,13 @@ export class PluginContext {
|
||||
reset();
|
||||
isBusy.next(false);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.behaviors.interaction.selectionMode.subscribe(v => {
|
||||
this.subs.push(this.behaviors.interaction.selectionMode.subscribe(v => {
|
||||
if (!v) {
|
||||
this.managers.interactivity?.lociSelects.deselectAll();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private initBuiltInBehavior() {
|
||||
@@ -315,7 +348,7 @@ export class PluginContext {
|
||||
BuiltInPluginBehaviors.Camera.registerDefault(this);
|
||||
BuiltInPluginBehaviors.Misc.registerDefault(this);
|
||||
|
||||
merge(this.state.data.events.log, this.state.behaviors.events.log).subscribe(e => this.events.log.next(e));
|
||||
this.subs.push(merge(this.state.data.events.log, this.state.behaviors.events.log).subscribe(e => this.events.log.next(e)));
|
||||
}
|
||||
|
||||
private async initBehaviors() {
|
||||
@@ -374,7 +407,7 @@ export class PluginContext {
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.events.log.subscribe(e => this.log.entries = this.log.entries.push(e));
|
||||
this.subs.push(this.events.log.subscribe(e => this.log.entries = this.log.entries.push(e)));
|
||||
|
||||
this.initCustomFormats();
|
||||
this.initBehaviorEvents();
|
||||
|
||||
@@ -72,7 +72,7 @@ export class PluginLayout extends StatefulPluginComponent<PluginLayoutStateProps
|
||||
this.events.updated.next();
|
||||
}
|
||||
|
||||
private root: HTMLElement | undefined;
|
||||
root: HTMLElement | undefined;
|
||||
private rootState: RootState | undefined = void 0;
|
||||
private expandedViewport: HTMLMetaElement;
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@ const ExtendersParams = {
|
||||
};
|
||||
type ExtendersParams = typeof ExtendersParams
|
||||
|
||||
const ArmsParams = {
|
||||
...LinesParams
|
||||
};
|
||||
type ArmsParams = typeof ArmsParams
|
||||
|
||||
const ArcParams = {
|
||||
...LinesParams
|
||||
};
|
||||
@@ -70,6 +75,7 @@ const DihedralVisuals = {
|
||||
'vectors': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, VectorsParams>) => ShapeRepresentation(getVectorsShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'extenders': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ExtendersParams>) => ShapeRepresentation(getExtendersShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'connector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ExtendersParams>) => ShapeRepresentation(getConnectorShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'arms': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ArmsParams>) => ShapeRepresentation(getArmsShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'arc': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ArcParams>) => ShapeRepresentation(getArcShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils, { modifyProps: p => ({ ...p, alpha: p.sectorOpacity }), modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, LociLabelTextParams>) => ShapeRepresentation(getTextShape, Text.Utils, { modifyState: s => ({ ...s, markerActions: MarkerAction.None }) }),
|
||||
@@ -78,10 +84,11 @@ const DihedralVisuals = {
|
||||
export const DihedralParams = {
|
||||
...VectorsParams,
|
||||
...ExtendersParams,
|
||||
...ArmsParams,
|
||||
...ArcParams,
|
||||
...SectorParams,
|
||||
...LociLabelTextParams,
|
||||
visuals: PD.MultiSelect(['extenders', 'sector', 'text'], PD.objectToOptions(DihedralVisuals)),
|
||||
visuals: PD.MultiSelect(['extenders', 'arms', 'sector', 'text'], PD.objectToOptions(DihedralVisuals)),
|
||||
};
|
||||
export type DihedralParams = typeof DihedralParams
|
||||
export type DihedralProps = PD.Values<DihedralParams>
|
||||
@@ -155,12 +162,12 @@ function setDihedralState(quad: Loci.Bundle<4>, state: DihedralState, arcScale:
|
||||
|
||||
Vec3.matchDirection(tmpVec, arcNormal, Vec3.sub(tmpVec, arcPointA, sphereA.center));
|
||||
const angleA = Vec3.angle(dirBA, tmpVec);
|
||||
const lenA = radius / Math.cos(angleA > halfPI ? angleA - halfPI : angleA);
|
||||
const lenA = radius / Math.cos(angleA - halfPI);
|
||||
Vec3.add(projA, sphereB.center, Vec3.setMagnitude(tmpVec, dirBA, lenA));
|
||||
|
||||
Vec3.matchDirection(tmpVec, arcNormal, Vec3.sub(tmpVec, arcPointD, sphereD.center));
|
||||
const angleD = Vec3.angle(dirCD, tmpVec);
|
||||
const lenD = radius / Math.cos(angleD > halfPI ? angleD - halfPI : angleD);
|
||||
const lenD = radius / Math.cos(angleD - halfPI);
|
||||
Vec3.add(projD, sphereC.center, Vec3.setMagnitude(tmpVec, dirCD, lenD));
|
||||
|
||||
return state;
|
||||
@@ -221,6 +228,24 @@ function getConnectorShape(ctx: RuntimeContext, data: DihedralData, props: Dihed
|
||||
|
||||
//
|
||||
|
||||
function buildArmsLines(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
|
||||
const builder = LinesBuilder.create(128, 64, lines);
|
||||
for (let i = 0, il = data.quads.length; i < il; ++i) {
|
||||
setDihedralState(data.quads[i], tmpState, props.arcScale);
|
||||
builder.addFixedLengthDashes(tmpState.sphereB.center, tmpState.sphereA.center, props.dashLength, i);
|
||||
builder.addFixedLengthDashes(tmpState.sphereC.center, tmpState.sphereD.center, props.dashLength, i);
|
||||
}
|
||||
return builder.getLines();
|
||||
}
|
||||
|
||||
function getArmsShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Lines>) {
|
||||
const lines = buildArmsLines(data, props, shape && shape.geometry);
|
||||
const name = getDihedralName(data);
|
||||
return Shape.create(name, data, lines, () => props.color, () => props.linesSize, () => '');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function buildExtendersLines(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
|
||||
const builder = LinesBuilder.create(128, 64, lines);
|
||||
for (let i = 0, il = data.quads.length; i < il; ++i) {
|
||||
@@ -299,7 +324,8 @@ function buildText(data: DihedralData, props: DihedralProps, text?: Text): Text
|
||||
Vec3.setMagnitude(tmpVec, tmpVec, tmpState.radius);
|
||||
Vec3.add(tmpVec, tmpState.arcCenter, tmpVec);
|
||||
|
||||
const angle = radToDeg(tmpState.angle).toFixed(2);
|
||||
let angle = radToDeg(tmpState.angle).toFixed(2);
|
||||
if (angle === '-0.00') angle = '0.00';
|
||||
const label = props.customText || `${angle}\u00B0`;
|
||||
const radius = Math.max(2, tmpState.sphereA.radius, tmpState.sphereB.radius, tmpState.sphereC.radius, tmpState.sphereD.radius);
|
||||
const scale = radius / 2;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user