Compare commits

...

17 Commits

Author SHA1 Message Date
David Sehnal
cd872b47e6 1.2.4 2020-12-03 15:30:30 +01:00
David Sehnal
2683c5b318 Merge pull request #105 from molstar/gpu-grid
GPU grid 3d computation wrapper
2020-12-03 15:24:43 +01:00
David Sehnal
c71f60a164 ParamDefinition.DataRef 2020-12-03 15:21:42 +01:00
David Sehnal
881cbc1947 tweaks 2020-12-03 13:54:51 +01:00
David Sehnal
f3e7febbd1 Merge branch 'master' of https://github.com/molstar/molstar into gpu-grid 2020-12-03 06:33:19 +01:00
David Sehnal
e68ad13031 createGrid3dComputeRenderable yieldPeriod param 2020-12-02 12:29:01 +01:00
Alexander Rose
7fbbe1e63a representation state and hightlight fixes
- recreate state when repr changes
- take repr into account for non-hover hightlights (eg from state tree)
2020-12-01 17:48:40 -08:00
Alexander Rose
a5ca72af3c postprocessing tweaks and fixes
- fix missing enable scissor state
- better antialiasing defaults
- always allow fxaa
2020-12-01 17:46:51 -08:00
David Sehnal
1ce6641eb3 grid3d-compute util code 2020-12-01 20:58:27 +01:00
David Sehnal
5dc413ab8c wip grid3d renderable 2020-12-01 19:33:05 +01:00
David Sehnal
50b615e86c 1.2.3 2020-11-28 14:50:15 +01:00
David Sehnal
5b4c6743e7 GlobalModelTransformInfo
- support in volume streaming
- export in ModelServer if transform param is present
2020-11-28 14:46:58 +01:00
Alexander Rose
99a3906978 1.2.2 2020-11-26 11:17:22 -08:00
Alexander Rose
981db34736 Merge branch 'master' of https://github.com/molstar/molstar 2020-11-26 11:12:35 -08:00
Alexander Rose
c079a8c5a8 fixed triple linkstyle in visuals
- was ignored
2020-11-26 11:12:11 -08:00
Alexander Rose
ad70adf6ce improved & fixed fxaa
- enable linear texture interpolation to actually do subpixel fetches...
- higher quality fxaa profile with edge exploration
- exposed parameters
- enable during temproal multi sampling
2020-11-26 11:11:14 -08:00
David Sehnal
89110b52bd copyright info 2020-11-26 15:55:48 +01:00
37 changed files with 1002 additions and 536 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "1.2.1",
"version": "1.2.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

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

View File

@@ -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).`

View File

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

View File

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

View File

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

View File

@@ -4,145 +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;
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);
}
// Adapted from https://github.com/equinor/glsl-float-to-rgba
// MIT License, Copyright (c) 2020 Equinor
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)
);
}
///////////////////////////////////////////////////////
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);
}
// Decode a 32-bit float from the RGBA color channels of a texel.
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;
}
@@ -193,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
}
@@ -229,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++) {
@@ -238,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;
@@ -254,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++) {
@@ -299,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);
}
}
`;

View File

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

View File

@@ -150,9 +150,10 @@ namespace Canvas3D {
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 antialias = (attribs.antialias ?? true) && !attribs.enableWboit;
const gl = getGLContext(canvas, {
alpha: true,
antialias: (attribs.antialias ?? true) && !attribs.enableWboit,
antialias,
depth: true,
preserveDrawingBuffer: true,
premultipliedAlpha: true,
@@ -197,6 +198,14 @@ namespace Canvas3D {
if (isDebugMode) console.log('context restored');
}, false);
// disable postprocessing anti-aliasing if canvas anti-aliasing is enabled
if (antialias && !props.postprocessing?.antialiasing) {
props.postprocessing = {
...DefaultCanvas3DParams.postprocessing,
antialiasing: { name: 'off', params: {} }
};
}
return create(webgl, input, passes, props, { pixelScale });
}

View File

@@ -75,7 +75,7 @@ export class DrawPass {
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.packedDepth = !extensions.depthTexture;
this.depthTarget = webgl.createRenderTarget(width, height);

View File

@@ -48,11 +48,14 @@ const PostprocessingShaderCode = ShaderCode('postprocessing', quad_vert, postpro
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture): PostprocessingRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof PostprocessingSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
tPackedDepth: ValueCell.create(depthTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
uTexSize: ValueCell.create(Vec2.create(width, height)),
dOrthographic: ValueCell.create(0),
uNear: ValueCell.create(1),
@@ -93,13 +96,21 @@ export const PostprocessingParams = {
}),
off: PD.Group({})
}, { cycle: true, description: 'Draw outline around 3D objects' }),
antialiasing: PD.Boolean(true, { description: 'Fast Approximate Anti-Aliasing (FXAA)' })
antialiasing: PD.MappedStatic('on', {
on: PD.Group({
edgeThresholdMin:PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }),
edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }),
iterations: PD.Numeric(12, { min: 0, max: 32, step: 1 }, { description: 'Number of edge exploration steps.' }),
subpixelQuality: PD.Numeric(1.00, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Fast Approximate Anti-Aliasing (FXAA)' }),
};
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
@@ -114,7 +125,7 @@ export class PostprocessingPass {
const height = colorTarget.getHeight();
this.target = webgl.createRenderTarget(width, height, false);
this.tmpTarget = webgl.createRenderTarget(width, height, false);
this.tmpTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture);
this.fxaa = getFxaaRenderable(webgl, this.tmpTarget.texture);
}
@@ -128,14 +139,14 @@ export class PostprocessingPass {
this.target.setSize(width, height);
this.tmpTarget.setSize(width, height);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.fxaa.values.uTexSize, Vec2.set(this.fxaa.values.uTexSize.ref.value, width, height));
ValueCell.update(this.fxaa.values.uTexSizeInv, Vec2.set(this.fxaa.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
}
}
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);
@@ -185,7 +196,7 @@ export class PostprocessingPass {
this.renderable.update();
}
if (props.antialiasing) {
if (props.antialiasing.name === 'on') {
this.tmpTarget.bind();
} else if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
@@ -198,10 +209,30 @@ export class PostprocessingPass {
}
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name === 'off') return;
const { values } = this.fxaa;
let needsUpdate = false;
const input = (props.occlusion.name === 'on' || props.outline.name === 'on')
? this.tmpTarget.texture : this.drawPass.colorTarget.texture;
if (this.fxaa.values.tColor.ref.value !== input) {
if (values.tColor.ref.value !== input) {
ValueCell.update(this.fxaa.values.tColor, input);
needsUpdate = true;
}
const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props.antialiasing.params;
if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin);
if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax);
if (values.dIterations.ref.value !== iterations) needsUpdate = true;
ValueCell.updateIfChanged(values.dIterations, iterations);
if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true;
ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality);
if (needsUpdate) {
this.fxaa.update();
}
@@ -216,11 +247,11 @@ export class PostprocessingPass {
}
private _render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.occlusion.name === 'on' || props.outline.name === 'on' || !props.antialiasing) {
if (props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'off') {
this._renderPostprocessing(camera, toDrawingBuffer, props);
}
if (props.antialiasing) {
if (props.antialiasing.name === 'on') {
this._renderFxaa(camera, toDrawingBuffer, props);
}
}
@@ -240,16 +271,29 @@ export class PostprocessingPass {
const FxaaSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uTexSizeInv: UniformSpec('v2'),
dEdgeThresholdMin: DefineSpec('number'),
dEdgeThresholdMax: DefineSpec('number'),
dIterations: DefineSpec('number'),
dSubpixelQuality: DefineSpec('number'),
};
const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag);
type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>>
function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof FxaaSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
dEdgeThresholdMin: ValueCell.create(0.0312),
dEdgeThresholdMax: ValueCell.create(0.125),
dIterations: ValueCell.create(12),
dSubpixelQuality: ValueCell.create(0.75),
};
const schema = { ...FxaaSchema };

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

View File

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

View File

@@ -151,7 +151,7 @@ export type ShaderDefines = {
[k: string]: ValueCell<DefineType>
}
function getDefinesCode (defines: ShaderDefines) {
function getDefinesCode(defines: ShaderDefines) {
if (defines === undefined) return '';
const lines = [];
for (const name in defines) {

View File

@@ -4,104 +4,226 @@ precision highp int;
precision highp sampler2D;
uniform sampler2D tColor;
uniform vec2 uTexSize;
uniform vec2 uTexSizeInv;
// Basic FXAA implementation based on the code on geeks3d.com with the
// modification that the texture2DLod stuff was removed since it's
// unsupported by WebGL.
// --
// From:
// https://github.com/mitsuhiko/webgl-meincraft
// Copyright (c) 2011 by Armin Ronacher.
// Some 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.
// * The names of the contributors may not 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
// OWNER 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.
// */
// adapted from https://github.com/kosua20/Rendu
// MIT License Copyright (c) 2017 Simon Rodriguez
#ifndef FXAA_REDUCE_MIN
#define FXAA_REDUCE_MIN (1.0/ 128.0)
#endif
#ifndef FXAA_REDUCE_MUL
#define FXAA_REDUCE_MUL (1.0 / 8.0)
#endif
#ifndef FXAA_SPAN_MAX
#define FXAA_SPAN_MAX 8.0
#endif
#define QUALITY(q) ((q) < 5 ? 1.0 : ((q) > 5 ? ((q) < 10 ? 2.0 : ((q) < 11 ? 4.0 : 8.0)) : 1.5))
vec4 fxaa(sampler2D tex, const in vec2 fragCoord, const in vec2 resolution) {
vec2 inverseVP = 1.0 / resolution;
vec2 v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP;
vec2 v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP;
vec2 v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP;
vec2 v_rgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP;
vec2 v_rgbM = vec2(fragCoord * inverseVP);
float rgb2luma(vec3 rgb){
return sqrt(dot(rgb, vec3(0.299, 0.587, 0.114)));
}
vec4 col = vec4(0.0);
vec3 rgbNW = texture2D(tex, v_rgbNW).xyz;
vec3 rgbNE = texture2D(tex, v_rgbNE).xyz;
vec3 rgbSW = texture2D(tex, v_rgbSW).xyz;
vec3 rgbSE = texture2D(tex, v_rgbSE).xyz;
vec4 texColor = texture2D(tex, v_rgbM);
vec3 rgbM = texColor.xyz;
vec3 luma = vec3(0.299, 0.587, 0.114);
float lumaNW = dot(rgbNW, luma);
float lumaNE = dot(rgbNE, luma);
float lumaSW = dot(rgbSW, luma);
float lumaSE = dot(rgbSE, luma);
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
float sampleLuma(vec2 uv) {
return rgb2luma(texture2D(tColor, uv).rgb);
}
vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
vec4 rgbA1 = texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5));
vec4 rgbA2 = texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5));
vec4 rgbA = 0.5 * (rgbA1 + rgbA2);
vec4 rgbB1 = texture2D(tex, fragCoord * inverseVP + dir * -0.5);
vec4 rgbB2 = texture2D(tex, fragCoord * inverseVP + dir * 0.5);
vec4 rgbB = rgbA * 0.5 + 0.25 * (rgbB1 + rgbB2);
float lumaB = dot(rgbB.rgb, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
col = vec4(rgbA.rgb, rgbA.a);
else
col = vec4(rgbB.rgb, rgbB.a);
return col;
float sampleLuma(vec2 uv, float uOffset, float vOffset) {
uv += uTexSizeInv * vec2(uOffset, vOffset);
return sampleLuma(uv);
}
void main(void) {
gl_FragColor = fxaa(tColor, gl_FragCoord.xy, uTexSize);
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
vec2 inverseScreenSize = uTexSizeInv;
vec4 colorCenter = texture2D(tColor, coords);
// Luma at the current fragment
float lumaCenter = rgb2luma(colorCenter.rgb);
// Luma at the four direct neighbours of the current fragment.
float lumaDown = sampleLuma(coords, 0.0, -1.0);
float lumaUp = sampleLuma(coords, 0.0, 1.0);
float lumaLeft = sampleLuma(coords, -1.0, 0.0);
float lumaRight = sampleLuma(coords, 1.0, 0.0);
// Find the maximum and minimum luma around the current fragment.
float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
// Compute the delta.
float lumaRange = lumaMax - lumaMin;
// If the luma variation is lower that a threshold (or if we are in a really dark area),
// we are not on an edge, don't perform any AA.
if (lumaRange < max(dEdgeThresholdMin, lumaMax * dEdgeThresholdMax)) {
gl_FragColor = colorCenter;
return;
}
// Query the 4 remaining corners lumas.
float lumaDownLeft = sampleLuma(coords, -1.0, -1.0);
float lumaUpRight = sampleLuma(coords, 1.0, 1.0);
float lumaUpLeft = sampleLuma(coords, -1.0, 1.0);
float lumaDownRight = sampleLuma(coords, 1.0, -1.0);
// Combine the four edges lumas (using intermediary variables for future computations
// with the same values).
float lumaDownUp = lumaDown + lumaUp;
float lumaLeftRight = lumaLeft + lumaRight;
// Same for corners
float lumaLeftCorners = lumaDownLeft + lumaUpLeft;
float lumaDownCorners = lumaDownLeft + lumaDownRight;
float lumaRightCorners = lumaDownRight + lumaUpRight;
float lumaUpCorners = lumaUpRight + lumaUpLeft;
// Compute an estimation of the gradient along the horizontal and vertical axis.
float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners);
float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners);
// Is the local edge horizontal or vertical ?
bool isHorizontal = (edgeHorizontal >= edgeVertical);
// Choose the step size (one pixel) accordingly.
float stepLength = isHorizontal ? inverseScreenSize.y : inverseScreenSize.x;
// Select the two neighboring texels lumas in the opposite direction to the local edge.
float luma1 = isHorizontal ? lumaDown : lumaLeft;
float luma2 = isHorizontal ? lumaUp : lumaRight;
// Compute gradients in this direction.
float gradient1 = luma1 - lumaCenter;
float gradient2 = luma2 - lumaCenter;
// Which direction is the steepest ?
bool is1Steepest = abs(gradient1) >= abs(gradient2);
// Gradient in the corresponding direction, normalized.
float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
// Average luma in the correct direction.
float lumaLocalAverage = 0.0;
if(is1Steepest){
// Switch the direction
stepLength = -stepLength;
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
} else {
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
}
// Shift UV in the correct direction by half a pixel.
vec2 currentUv = coords;
if(isHorizontal){
currentUv.y += stepLength * 0.5;
} else {
currentUv.x += stepLength * 0.5;
}
// Compute offset (for each iteration step) in the right direction.
vec2 offset = isHorizontal ? vec2(inverseScreenSize.x, 0.0) : vec2(0.0, inverseScreenSize.y);
// Compute UVs to explore on each side of the edge, orthogonally.
// The QUALITY allows us to step faster.
vec2 uv1 = currentUv - offset * QUALITY(0);
vec2 uv2 = currentUv + offset * QUALITY(0);
// Read the lumas at both current extremities of the exploration segment,
// and compute the delta wrt to the local average luma.
float lumaEnd1 = sampleLuma(uv1);
float lumaEnd2 = sampleLuma(uv2);
lumaEnd1 -= lumaLocalAverage;
lumaEnd2 -= lumaLocalAverage;
// If the luma deltas at the current extremities is larger than the local gradient,
// we have reached the side of the edge.
bool reached1 = abs(lumaEnd1) >= gradientScaled;
bool reached2 = abs(lumaEnd2) >= gradientScaled;
bool reachedBoth = reached1 && reached2;
// If the side is not reached, we continue to explore in this direction.
if(!reached1){
uv1 -= offset * QUALITY(1);
}
if(!reached2){
uv2 += offset * QUALITY(1);
}
// If both sides have not been reached, continue to explore.
if(!reachedBoth){
for(int i = 2; i < dIterations; i++){
// If needed, read luma in 1st direction, compute delta.
if(!reached1){
lumaEnd1 = sampleLuma(uv1);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
}
// If needed, read luma in opposite direction, compute delta.
if(!reached2){
lumaEnd2 = sampleLuma(uv2);
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
}
// If the luma deltas at the current extremities is larger than the local gradient,
// we have reached the side of the edge.
reached1 = abs(lumaEnd1) >= gradientScaled;
reached2 = abs(lumaEnd2) >= gradientScaled;
reachedBoth = reached1 && reached2;
// If the side is not reached, we continue to explore in this direction,
// with a variable quality.
if(!reached1){
uv1 -= offset * QUALITY(i);
}
if(!reached2){
uv2 += offset * QUALITY(i);
}
// If both sides have been reached, stop the exploration.
if(reachedBoth){
break;
}
}
}
// Compute the distances to each side edge of the edge (!).
float distance1 = isHorizontal ? (coords.x - uv1.x) : (coords.y - uv1.y);
float distance2 = isHorizontal ? (uv2.x - coords.x) : (uv2.y - coords.y);
// In which direction is the side of the edge closer ?
bool isDirection1 = distance1 < distance2;
float distanceFinal = min(distance1, distance2);
// Thickness of the edge.
float edgeThickness = (distance1 + distance2);
// Is the luma at center smaller than the local average ?
bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
// If the luma at center is smaller than at its neighbour,
// the delta luma at each end should be positive (same variation).
bool correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
bool correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
// Only keep the result in the direction of the closer side of the edge.
bool correctVariation = isDirection1 ? correctVariation1 : correctVariation2;
// UV offset: read in the direction of the closest side of the edge.
float pixelOffset = - distanceFinal / edgeThickness + 0.5;
// If the luma variation is incorrect, do not offset.
float finalOffset = correctVariation ? pixelOffset : 0.0;
// Sub-pixel shifting
// Full weighted average of the luma over the 3x3 neighborhood.
float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
// Ratio of the delta between the global average and the center luma,
// over the luma range in the 3x3 neighborhood.
float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
// Compute a sub-pixel offset based on this delta.
float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * float(dSubpixelQuality);
// Pick the biggest of the two offsets.
finalOffset = max(finalOffset, subPixelOffsetFinal);
// Compute the final UV coordinates.
vec2 finalUv = coords;
if(isHorizontal){
finalUv.y += finalOffset * stepLength;
} else {
finalUv.x += finalOffset * stepLength;
}
// Read the color at the new UV coordinates, and use it.
gl_FragColor = texture2D(tColor, finalUv);
}
`;

View File

@@ -0,0 +1,181 @@
/**
* 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}
//////////////////////////////////////////////////////////
// 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.
#ifdef CUMULATIVE
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);
}
#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)));
#endif
gl_FragColor = floatToRgba({RETURN});
}
`;

View File

@@ -22,7 +22,7 @@ import { VertexArray, createVertexArray } from './vertex-array';
function defineValueHash(v: boolean | number | string): number {
return typeof v === 'boolean' ? (v ? 1 : 0) :
typeof v === 'number' ? v : hashString(v);
typeof v === 'number' ? (v * 10000) : hashString(v);
}
function wrapCached<T extends Resource>(resourceItem: ReferenceItem<T>) {

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -117,7 +117,7 @@ class ViewportScreenshotHelper extends PluginComponent {
},
postprocessing: {
...c.props.postprocessing,
antialiasing: false
antialiasing: { name: 'off', params: {} }
}
});
}
@@ -136,7 +136,7 @@ class ViewportScreenshotHelper extends PluginComponent {
// TODO: optimize because this creates a copy of a large object!
postprocessing: {
...this.plugin.canvas3d!.props.postprocessing,
antialiasing: false
antialiasing: { name: 'off', params: {} }
}
});
return this._imagePass;

View File

@@ -131,7 +131,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 7, cylinderProps);
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
const order = LinkStyle.Double ? 2 : 3;
const order = linkStyle === LinkStyle.Double ? 2 : 3;
const multiRadius = linkRadius * (linkScale / (0.5 * order));
const absOffset = (linkRadius - multiRadius) * linkSpacing;

View File

@@ -328,6 +328,8 @@ class State {
const oldTree = this._tree;
this._tree = _tree;
const cells = this.cells;
const ctx: UpdateContext = {
parent: this,
editInfo: StateBuilder.is(tree) ? tree.editInfo : void 0,
@@ -346,7 +348,9 @@ class State {
changed: false,
hadError: false,
wasAborted: false,
newCurrent: void 0
newCurrent: void 0,
getCellData: ref => cells.get(ref)!.obj?.data
};
this.errorFree = true;
@@ -454,7 +458,9 @@ interface UpdateContext {
changed: boolean,
hadError: boolean,
wasAborted: boolean,
newCurrent?: Ref
newCurrent?: Ref,
getCellData: (ref: string) => any
}
async function update(ctx: UpdateContext) {
@@ -841,7 +847,7 @@ function resolveParams(ctx: UpdateContext, transform: StateTransform, src: State
(transform.params as any) = transform.params
? assignIfUndefined(transform.params, defaultValues)
: defaultValues;
ParamDefinition.resolveValueRefs(definition, transform.params);
ParamDefinition.resolveRefs(definition, transform.params, ctx.getCellData);
return { definition, values: transform.params };
}

View File

@@ -296,6 +296,13 @@ export namespace ParamDefinition {
return setInfo<ValueRef<T>>({ type: 'value-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any }, getOptions, resolveRef }, info);
}
export interface DataRef<T = any> extends Base<{ ref: string, getValue: () => T }> {
type: 'data-ref'
}
export function DataRef<T>(info?: Info & { defaultRef?: string }) {
return setInfo<DataRef<T>>({ type: 'data-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any } }, info);
}
export interface Converted<T, C> extends Base<T> {
type: 'converted',
converted: Any,
@@ -329,7 +336,7 @@ export namespace ParamDefinition {
export type Any =
| Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Mat4 | Numeric | FileParam | UrlParam | FileListParam | Interval | LineGraph
| ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef
| ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef | DataRef
export type Params = { [k: string]: Any }
export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }
@@ -360,29 +367,33 @@ export namespace ParamDefinition {
return () => resolve(ref);
}
function resolveRefValue(p: Any, value: any) {
function resolveRefValue(p: Any, value: any, getData: (ref: string) => any) {
if (!value) return;
if (p.type === 'value-ref') {
const v = value as ValueRef['defaultValue'];
if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); };
else v.getValue = _resolveRef(p.resolveRef, v.ref);
} else if (p.type === 'data-ref') {
const v = value as ValueRef['defaultValue'];
if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); };
else v.getValue = _resolveRef(getData, v.ref);
} else if (p.type === 'group') {
resolveValueRefs(p.params, value);
resolveRefs(p.params, value, getData);
} else if (p.type === 'mapped') {
const v = value as NamedParams;
const param = p.map(v.name);
resolveRefValue(param, v.params);
resolveRefValue(param, v.params, getData);
} else if (p.type === 'object-list') {
if (!hasValueRef(p.element)) return;
for (const e of value) {
resolveValueRefs(p.element, e);
resolveRefs(p.element, e, getData);
}
}
}
function hasParamValueRef(p: Any) {
if (p.type === 'value-ref') {
if (p.type === 'value-ref' || p.type === 'data-ref') {
return true;
} else if (p.type === 'group') {
if (hasValueRef(p.params)) return true;
@@ -403,9 +414,9 @@ export namespace ParamDefinition {
return false;
}
export function resolveValueRefs(params: Params, values: any) {
export function resolveRefs(params: Params, values: any, getData: (ref: string) => any) {
for (const n of Object.keys(params)) {
resolveRefValue(params[n], values?.[n]);
resolveRefValue(params[n], values?.[n], getData);
}
}

View File

@@ -1,3 +1,6 @@
# 0.9.5
* Support molstar_global_model_transform_info category.
# 0.9.4
* bug fix for /ligand queries on metal ions

View File

@@ -30,6 +30,7 @@ import { MolEncoder } from '../../../mol-io/writer/mol/encoder';
import { Mol2Encoder } from '../../../mol-io/writer/mol2/encoder';
import { ComponentAtom } from '../../../mol-model-formats/structure/property/atoms/chem_comp';
import { Mat4 } from '../../../mol-math/linear-algebra';
import { GlobalModelTransformInfo } from '../../../mol-model/structure/model/properties/global-transform';
export interface Stats {
structure: StructureWrapper,
@@ -247,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform);
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
perf.end('encode');

View File

@@ -4,4 +4,4 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default '0.9.4';
export default '0.9.5';