mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 15:14:22 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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.4",
|
||||
"version": "1.2.9",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
@@ -117,6 +117,7 @@ interface Canvas3D {
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
readonly didDraw: BehaviorSubject<now.Timestamp>
|
||||
readonly commited: BehaviorSubject<now.Timestamp>
|
||||
readonly reprCount: BehaviorSubject<number>
|
||||
readonly resized: BehaviorSubject<any>
|
||||
|
||||
@@ -218,6 +219,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;
|
||||
|
||||
@@ -391,6 +393,7 @@ namespace Canvas3D {
|
||||
draw(true);
|
||||
forceDrawAfterAllCommited = false;
|
||||
}
|
||||
commited.next(now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +619,7 @@ namespace Canvas3D {
|
||||
get notifyDidDraw() { return notifyDidDraw; },
|
||||
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
|
||||
didDraw,
|
||||
commited,
|
||||
reprCount,
|
||||
resized,
|
||||
setProps: (properties, doNotRequestDraw = false) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ export namespace BaseGeometry {
|
||||
colorOnly: false,
|
||||
opaque,
|
||||
writeDepth: opaque,
|
||||
noClip: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -126,6 +128,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),
|
||||
};
|
||||
}
|
||||
@@ -142,6 +146,8 @@ export namespace TextureMesh {
|
||||
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')
|
||||
|
||||
@@ -94,7 +94,8 @@ function createPoints() {
|
||||
pickable: true,
|
||||
colorOnly: false,
|
||||
opaque: true,
|
||||
writeDepth: true
|
||||
writeDepth: true,
|
||||
noClip: false,
|
||||
};
|
||||
|
||||
return createRenderObject('points', values, state, -1);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* 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, 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';
|
||||
@@ -26,37 +26,44 @@ const HistopyramidReductionSchema = {
|
||||
uTexSize: UniformSpec('f'),
|
||||
};
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
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 +77,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,8 +107,8 @@ 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;
|
||||
|
||||
// printTexture(ctx, inputTexture, 2)
|
||||
if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
|
||||
@@ -95,13 +118,14 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
// This part set the levels
|
||||
const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2));
|
||||
const maxSize = Math.pow(2, levels);
|
||||
// console.log('levels', levels, 'maxSize', maxSize)
|
||||
// console.log('levels', levels, 'maxSize', maxSize, 'input', inputTexture.getWidth());
|
||||
|
||||
const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
const pyramidTexture = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
pyramidTexture.define(maxSize, maxSize);
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
const framebuffer = getFramebuffer('pyramid', ctx);
|
||||
pyramidTexture.attachFramebuffer(framebuffer, 0);
|
||||
gl.viewport(0, 0, maxSize, maxSize);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
const levelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
@@ -120,8 +144,6 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
|
||||
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);
|
||||
@@ -130,6 +152,10 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
const activeVoxelsTex = ctx.namedTextures[ActiveVoxelsName];
|
||||
activeVoxelsTex.define(width, height);
|
||||
|
||||
const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale);
|
||||
@@ -80,6 +104,9 @@ 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)
|
||||
|
||||
@@ -38,18 +38,46 @@ 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.uQuadScale, Vec2.create(1, height / Math.pow(2, levels)));
|
||||
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()),
|
||||
|
||||
uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
|
||||
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 +85,6 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
uGridTransform: ValueCell.create(transform),
|
||||
|
||||
uScale: ValueCell.create(scale),
|
||||
};
|
||||
|
||||
@@ -86,27 +113,23 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
|
||||
// console.log('iso volumeData', volumeData)
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
if (!ctx.namedFramebuffers[IsosurfaceName]) {
|
||||
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
|
||||
}
|
||||
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
|
||||
|
||||
let needsClear = false;
|
||||
const w = pyramidTex.getWidth();
|
||||
const h = pyramidTex.getHeight();
|
||||
|
||||
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(w, h);
|
||||
|
||||
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(w, h);
|
||||
|
||||
// const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// infoTex.define(pyramidTex.width, pyramidTex.height)
|
||||
@@ -147,11 +170,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, w, h);
|
||||
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> {
|
||||
|
||||
@@ -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>
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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 { 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);
|
||||
|
||||
@@ -146,14 +150,19 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
if (!texture) texture = colorBufferFloat && textureFloat
|
||||
? resources.texture('image-float32', 'rgba', 'float', 'linear')
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
texture.define(texDimX, texDimY);
|
||||
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 +173,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,12 +194,12 @@ 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 { smoothness, resolution } = props;
|
||||
@@ -213,6 +223,7 @@ 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')
|
||||
@@ -239,7 +250,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 +314,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 +348,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 +366,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 +420,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);
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { Visual } from '../visual';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
|
||||
export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
|
||||
|
||||
@@ -216,7 +217,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
|
||||
Representation.updateState(_state, state);
|
||||
},
|
||||
setTheme(theme: Theme) {
|
||||
console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
|
||||
if(isDebugMode) {
|
||||
console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
// TODO
|
||||
|
||||
@@ -298,6 +298,12 @@ export function ComplexTextVisual<P extends ComplexTextParams>(builder: ComplexT
|
||||
if (newProps.tetherLength !== currentProps.tetherLength) state.createGeometry = true;
|
||||
if (newProps.tetherBaseWidth !== currentProps.tetherBaseWidth) state.createGeometry = true;
|
||||
if (newProps.attachment !== currentProps.attachment) state.createGeometry = true;
|
||||
|
||||
if (newProps.fontFamily !== currentProps.fontFamily) state.createGeometry = true;
|
||||
if (newProps.fontQuality !== currentProps.fontQuality) state.createGeometry = true;
|
||||
if (newProps.fontStyle !== currentProps.fontStyle) state.createGeometry = true;
|
||||
if (newProps.fontVariant !== currentProps.fontVariant) state.createGeometry = true;
|
||||
if (newProps.fontWeight !== currentProps.fontWeight) state.createGeometry = true;
|
||||
},
|
||||
geometryUtils: Text.Utils
|
||||
}, materialId);
|
||||
|
||||
@@ -382,6 +382,12 @@ export function UnitsTextVisual<P extends UnitsTextParams>(builder: UnitsTextVis
|
||||
if (newProps.tetherLength !== currentProps.tetherLength) state.createGeometry = true;
|
||||
if (newProps.tetherBaseWidth !== currentProps.tetherBaseWidth) state.createGeometry = true;
|
||||
if (newProps.attachment !== currentProps.attachment) state.createGeometry = true;
|
||||
|
||||
if (newProps.fontFamily !== currentProps.fontFamily) state.createGeometry = true;
|
||||
if (newProps.fontQuality !== currentProps.fontQuality) state.createGeometry = true;
|
||||
if (newProps.fontStyle !== currentProps.fontStyle) state.createGeometry = true;
|
||||
if (newProps.fontVariant !== currentProps.fontVariant) state.createGeometry = true;
|
||||
if (newProps.fontWeight !== currentProps.fontWeight) state.createGeometry = true;
|
||||
},
|
||||
geometryUtils: Text.Utils
|
||||
}, materialId);
|
||||
|
||||
@@ -33,10 +33,10 @@ export type GaussianSurfaceMeshParams = typeof GaussianSurfaceMeshParams
|
||||
|
||||
async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
|
||||
const { smoothness } = props;
|
||||
const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
isoLevel: Math.exp(-smoothness),
|
||||
isoLevel: Math.exp(-smoothness) / radiusFactor,
|
||||
scalarField: field,
|
||||
idField
|
||||
};
|
||||
@@ -81,10 +81,10 @@ export type StructureGaussianSurfaceMeshParams = typeof StructureGaussianSurface
|
||||
|
||||
async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
|
||||
const { smoothness } = props;
|
||||
const { transform, field, idField } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
isoLevel: Math.exp(-smoothness),
|
||||
isoLevel: Math.exp(-smoothness) / radiusFactor,
|
||||
scalarField: field,
|
||||
idField
|
||||
};
|
||||
@@ -119,29 +119,49 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
|
||||
|
||||
//
|
||||
|
||||
const GaussianSurfaceName = 'gaussian-surface';
|
||||
|
||||
async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, textureMesh?: TextureMesh): Promise<TextureMesh> {
|
||||
if (!ctx.webgl) throw new Error('webgl context required to create gaussian surface texture-mesh');
|
||||
const isoLevel = Math.exp(-props.smoothness);
|
||||
|
||||
const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
const { namedTextures, resources, extensions: { colorBufferFloat, textureFloat } } = ctx.webgl;
|
||||
if (!namedTextures[GaussianSurfaceName]) {
|
||||
namedTextures[GaussianSurfaceName] = colorBufferFloat && textureFloat
|
||||
? resources.texture('image-float32', 'rgba', 'float', 'linear')
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
}
|
||||
|
||||
// console.time('computeUnitGaussianDensityTexture2d');
|
||||
const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
|
||||
// console.log(densityTextureData)
|
||||
// console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture))
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync()
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('computeUnitGaussianDensityTexture2d');
|
||||
|
||||
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
|
||||
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, isoLevel, densityTextureData.gridTexScale);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync()
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
|
||||
const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, densityTextureData.gridTexScale);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync()
|
||||
// console.time('createHistogramPyramid');
|
||||
const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, densityTextureData.gridTexScale, densityTextureData.gridTexDim);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createHistogramPyramid');
|
||||
|
||||
// console.time('createIsosurfaceBuffers');
|
||||
const gv = createIsosurfaceBuffers(ctx.webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoLevel, textureMesh ? textureMesh.vertexGroupTexture.ref.value : undefined, textureMesh ? textureMesh.normalTexture.ref.value : undefined);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync()
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
|
||||
// const boundingSphere = Sphere3D.zero()
|
||||
// Sphere3D.addVec3(boundingSphere, boundingSphere, densityTextureData.gridDimension)
|
||||
const boundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox);
|
||||
const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, boundingSphere, textureMesh);
|
||||
|
||||
// console.log({
|
||||
// renderables: ctx.webgl.namedComputeRenderables,
|
||||
// framebuffers: ctx.webgl.namedFramebuffers,
|
||||
// textures: ctx.webgl.namedTextures,
|
||||
// });
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync()
|
||||
return surface;
|
||||
}
|
||||
|
||||
@@ -54,12 +54,12 @@ export function computeUnitGaussianDensityTexture(structure: Structure, unit: Un
|
||||
});
|
||||
}
|
||||
|
||||
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
|
||||
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, powerOfTwo: boolean, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return GaussianDensityTexture2d(webgl, position, box, radius, p, texture);
|
||||
return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const bbox = getBoundingBox(gridDimension, transform);
|
||||
|
||||
// TODO: handle disposal
|
||||
const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
texture.load(textureImage);
|
||||
|
||||
@@ -195,6 +196,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const bbox = getBoundingBox(gridDimension, transform);
|
||||
|
||||
// TODO: handle disposal
|
||||
const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
|
||||
texture.load(textureVolume);
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 0.9.6
|
||||
* optional download parameter
|
||||
|
||||
# 0.9.5
|
||||
* Support molstar_global_model_transform_info category.
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ export interface MultipleQueryEntry<Name extends QueryName = QueryName> {
|
||||
export interface MultipleQuerySpec {
|
||||
queries: MultipleQueryEntry[],
|
||||
encoding?: Encoding,
|
||||
asTarGz?: boolean
|
||||
asTarGz?: boolean,
|
||||
download?: boolean
|
||||
}
|
||||
|
||||
export function getMultiQuerySpecFilename() {
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as bodyParser from 'body-parser';
|
||||
import { ModelServerConfig as Config, ModelServerConfig, mapSourceAndIdToFilename } from '../config';
|
||||
import { ConsoleLogger } from '../../../mol-util/console-logger';
|
||||
import { resolveJob } from './query';
|
||||
import { JobManager, JobEntry } from './jobs';
|
||||
import { JobManager, JobEntry, ResultWriterParams } from './jobs';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { QueryDefinition, normalizeRestQueryParams, normalizeRestCommonParams, QueryList } from './api';
|
||||
import { getApiSchema, shortcutIconLink } from './api-schema';
|
||||
@@ -45,17 +45,18 @@ async function processNextJob() {
|
||||
}
|
||||
}
|
||||
|
||||
export function createResultWriter(response: express.Response, encoding: string, entryId?: string, queryName?: string) {
|
||||
const filenameBase = entryId && queryName
|
||||
? `${entryId}_${splitCamelCase(queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
|
||||
export function createResultWriter(response: express.Response, params: ResultWriterParams) {
|
||||
const filenameBase = params.entryId && params.queryName
|
||||
? `${params.entryId}_${splitCamelCase(params.queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
|
||||
: `result`;
|
||||
return new SimpleResponseResultWriter(`${filenameBase}.${encoding}`, response, encoding === 'bcif');
|
||||
return new SimpleResponseResultWriter(`${filenameBase}.${params.encoding}`, response, params.encoding === 'bcif', params.download);
|
||||
}
|
||||
|
||||
function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
|
||||
function createJob(queryParams: any, req: express.Request, res: express.Response) {
|
||||
const entryId = req.params.id;
|
||||
const commonParams = normalizeRestCommonParams(req.query);
|
||||
const resultWriterParams = { encoding: commonParams.encoding!, download: !!commonParams.download, entryId, queryName };
|
||||
const jobId = JobManager.add({
|
||||
entries: [JobEntry({
|
||||
sourceId: commonParams.data_source || ModelServerConfig.defaultSource,
|
||||
@@ -66,7 +67,7 @@ function mapQuery(app: express.Express, queryName: string, queryDefinition: Quer
|
||||
copyAllCategories: !!commonParams.copy_all_categories,
|
||||
transform: commonParams.transform
|
||||
})],
|
||||
writer: createResultWriter(res, commonParams.encoding!, entryId, queryName),
|
||||
writer: createResultWriter(res, resultWriterParams),
|
||||
options: { binary: commonParams.encoding === 'bcif', encoding: commonParams.encoding }
|
||||
});
|
||||
responseMap.set(jobId, res);
|
||||
@@ -122,7 +123,7 @@ function serveStatic(req: express.Request, res: express.Response) {
|
||||
function createMultiJob(spec: MultipleQuerySpec, res: express.Response) {
|
||||
const writer = spec.asTarGz
|
||||
? new TarballResponseResultWriter(getMultiQuerySpecFilename(), res)
|
||||
: createResultWriter(res, spec.encoding!);
|
||||
: createResultWriter(res, { encoding: spec.encoding!, download: !!spec.download });
|
||||
|
||||
if (spec.queries.length > ModelServerConfig.maxQueryManyQueries) {
|
||||
writer.doError(400, `query-many queries limit (${ModelServerConfig.maxQueryManyQueries}) exceeded.`);
|
||||
|
||||
@@ -48,7 +48,8 @@ export const CommonQueryParamsInfo: QueryParamInfo[] = [
|
||||
{ name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF', 'MOL', or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'sdf', 'mol', 'mol2'] },
|
||||
{ name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
|
||||
{ name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' },
|
||||
{ name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` }
|
||||
{ name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` },
|
||||
{ name: 'download', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, browser will download text files.' }
|
||||
];
|
||||
|
||||
export type Encoding = 'cif' | 'bcif' | 'sdf' | 'mol' | 'mol2';
|
||||
@@ -57,7 +58,8 @@ export interface CommonQueryParamsInfo {
|
||||
encoding?: Encoding,
|
||||
copy_all_categories?: boolean
|
||||
data_source?: string,
|
||||
transform?: Mat4
|
||||
transform?: Mat4,
|
||||
download?: boolean
|
||||
}
|
||||
|
||||
export const AtomSiteSchemaElement = {
|
||||
@@ -290,12 +292,20 @@ export function normalizeRestCommonParams(params: any): CommonQueryParamsInfo {
|
||||
return {
|
||||
model_nums: params.model_nums ? ('' + params.model_nums).split(',').map(n => n.trim()).filter(n => !!n).map(n => +n) : void 0,
|
||||
data_source: params.data_source,
|
||||
copy_all_categories: Boolean(params.copy_all_categories),
|
||||
copy_all_categories: isTrue(params.copy_all_categories),
|
||||
encoding: mapEncoding(('' + params.encoding).toLocaleLowerCase()),
|
||||
transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity()
|
||||
transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity(),
|
||||
download: isTrue(params.download)
|
||||
};
|
||||
}
|
||||
|
||||
function isTrue(val: any): boolean {
|
||||
const b = Boolean(val);
|
||||
if (!b) return false;
|
||||
if (typeof val === 'string') return val !== '0' && val.toLowerCase() !== 'false';
|
||||
return b;
|
||||
}
|
||||
|
||||
function mapEncoding(value: string) {
|
||||
switch (value) {
|
||||
case 'bcif':
|
||||
|
||||
@@ -56,6 +56,13 @@ interface JobEntryDefinition<Name extends QueryName> {
|
||||
transform?: Mat4
|
||||
}
|
||||
|
||||
export interface ResultWriterParams {
|
||||
encoding: Encoding,
|
||||
download: boolean,
|
||||
entryId?: string,
|
||||
queryName?: string
|
||||
}
|
||||
|
||||
export function JobEntry<Name extends QueryName>(definition: JobEntryDefinition<Name>): JobEntry {
|
||||
const queryDefinition = getQueryByName(definition.queryName);
|
||||
if (!queryDefinition) throw new Error(`Query '${definition.queryName}' is not supported.`);
|
||||
|
||||
@@ -48,10 +48,11 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
|
||||
this.headerWritten = true;
|
||||
|
||||
this.res.writeHead(200, {
|
||||
// TODO there seems to be a bug in swagger-ui - front-end will freeze for cif delivered as text/plain (forcing binary is a hack to circumvent this)
|
||||
'Content-Type': this.isBinary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'X-Requested-With',
|
||||
'Content-Disposition': `inline; filename="${this.fn}"`
|
||||
'Content-Disposition': `${this.isDownload ? 'attachment' : 'inline'}; filename="${this.fn}"`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
|
||||
this.ended = true;
|
||||
}
|
||||
|
||||
constructor(private fn: string, private res: express.Response, private isBinary: boolean) {
|
||||
constructor(private fn: string, private res: express.Response, private isBinary: boolean, private isDownload: boolean) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export default '0.9.5';
|
||||
export default '0.9.6';
|
||||
@@ -68,7 +68,7 @@ async function init() {
|
||||
console.timeEnd('gpu mc active2');
|
||||
|
||||
console.time('gpu mc pyramid2');
|
||||
const compacted2 = createHistogramPyramid(webgl, activeVoxelsTex2, densityTextureData2.gridTexScale);
|
||||
const compacted2 = createHistogramPyramid(webgl, activeVoxelsTex2, densityTextureData2.gridTexScale, densityTextureData2.gridTexDim);
|
||||
webgl.waitForGpuCommandsCompleteSync();
|
||||
console.timeEnd('gpu mc pyramid2');
|
||||
|
||||
@@ -91,7 +91,7 @@ async function init() {
|
||||
console.timeEnd('gpu mc active');
|
||||
|
||||
console.time('gpu mc pyramid');
|
||||
const compacted = createHistogramPyramid(webgl, activeVoxelsTex, densityTextureData.gridTexScale);
|
||||
const compacted = createHistogramPyramid(webgl, activeVoxelsTex, densityTextureData.gridTexScale, densityTextureData.gridTexDim);
|
||||
webgl.waitForGpuCommandsCompleteSync();
|
||||
console.timeEnd('gpu mc pyramid');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user