mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 15:14:22 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6f61ea06b | ||
|
|
3095754817 | ||
|
|
c76c433410 | ||
|
|
347ef3ea7a | ||
|
|
84c47738ac | ||
|
|
fe331ead80 | ||
|
|
77d013b775 | ||
|
|
02a466e8b9 | ||
|
|
3cb65cbe3d | ||
|
|
fe8838542c | ||
|
|
78b5c9aac4 | ||
|
|
021fa7b79b | ||
|
|
0443589b09 | ||
|
|
415288de9f | ||
|
|
ecbafb086a | ||
|
|
e5dae6c0dd | ||
|
|
16f4524bdb | ||
|
|
6b33021f43 | ||
|
|
fdf37100c2 | ||
|
|
e28674d0dc | ||
|
|
fb7456286a | ||
|
|
9d240f8928 | ||
|
|
48ef5efb21 | ||
|
|
52b2e7c144 | ||
|
|
f2d1d60f6b | ||
|
|
5a176a378a | ||
|
|
60151c2c24 | ||
|
|
a5db6350a2 | ||
|
|
0618eb18ba | ||
|
|
bffdff6aad | ||
|
|
7753a6ec56 | ||
|
|
b8aafa1d78 | ||
|
|
672875187b | ||
|
|
547d60d573 | ||
|
|
99471d2a7b | ||
|
|
45d249b71a | ||
|
|
1382edd81c |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "1.2.13",
|
||||
"version": "1.3.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "1.2.13",
|
||||
"version": "1.2.15",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "1.2.13",
|
||||
"version": "1.3.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -88,7 +88,6 @@ export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGen
|
||||
isApplicable: (ctx: ThemeDataContext) => {
|
||||
return (
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
|
||||
!!CellPackInfoProvider.get(ctx.structure).value
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -396,21 +396,25 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
|
||||
const builder = Structure.Builder({ label: name });
|
||||
const units: Unit[] = [];
|
||||
let offsetInvariantId = 0;
|
||||
let offsetChainGroupId = 0;
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
|
||||
let maxInvariantId = 0;
|
||||
let maxChainGroupId = 0;
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId;
|
||||
const chainGroupId = u.chainGroupId + offsetChainGroupId;
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
|
||||
units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
|
||||
}
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
offsetChainGroupId += maxChainGroupId + 1;
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = builder.getStructure();
|
||||
const structure = new Structure(units);
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
|
||||
@@ -520,6 +520,7 @@ namespace Canvas3D {
|
||||
drawCount: r.values.drawCount.ref.value,
|
||||
instanceCount: r.values.instanceCount.ref.value,
|
||||
materialId: r.materialId,
|
||||
renderItemId: r.id,
|
||||
})));
|
||||
console.log(webgl.stats);
|
||||
|
||||
@@ -692,12 +693,10 @@ namespace Canvas3D {
|
||||
reprCount,
|
||||
resized,
|
||||
setProps: (properties, doNotRequestDraw = false) => {
|
||||
let props: PartialCanvas3DProps = typeof properties === 'function'
|
||||
const props: PartialCanvas3DProps = typeof properties === 'function'
|
||||
? produce(getProps(), properties)
|
||||
: properties;
|
||||
|
||||
props = PD.normalizeParams(Canvas3DParams, props, 'children');
|
||||
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null);
|
||||
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
|
||||
cameraState.mode = props.camera.mode;
|
||||
|
||||
@@ -36,8 +36,8 @@ export const DefaultTrackballBindings = {
|
||||
export const TrackballControlsParams = {
|
||||
noScroll: PD.Boolean(true, { isHidden: true }),
|
||||
|
||||
rotateSpeed: PD.Numeric(3.0, { min: 0.1, max: 10, step: 0.1 }),
|
||||
zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
|
||||
rotateSpeed: PD.Numeric(5.0, { min: 1, max: 10, step: 1 }),
|
||||
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
|
||||
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
|
||||
|
||||
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
|
||||
@@ -138,7 +138,8 @@ namespace TrackballControls {
|
||||
const dy = _rotCurr[1] - _rotPrev[1];
|
||||
Vec3.set(rotMoveDir, dx, dy, 0);
|
||||
|
||||
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio;
|
||||
const aspectRatio = input.width / input.height;
|
||||
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
|
||||
|
||||
if (angle) {
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
@@ -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, noClip: false }, materialId);
|
||||
return createRenderObject('mesh', values, { disposed: false, visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
|
||||
}
|
||||
@@ -237,10 +237,10 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
export const PostprocessingParams = {
|
||||
occlusion: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
samples: PD.Numeric(64, {min: 1, max: 256, step: 1}),
|
||||
samples: PD.Numeric(32, {min: 1, max: 256, step: 1}),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
|
||||
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
|
||||
blurKernelSize: PD.Numeric(20, { min: 1, max: 25, step: 2 }),
|
||||
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
@@ -286,7 +286,11 @@ export class PostprocessingPass {
|
||||
|
||||
private readonly renderable: PostprocessingRenderable
|
||||
|
||||
private scale: number
|
||||
|
||||
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
|
||||
this.scale = 1 / this.webgl.pixelRatio;
|
||||
|
||||
const { colorTarget, depthTexture } = drawPass;
|
||||
const width = colorTarget.getWidth();
|
||||
const height = colorTarget.getHeight();
|
||||
@@ -313,12 +317,15 @@ export class PostprocessingPass {
|
||||
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
|
||||
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthTexture.define(width, height);
|
||||
const sw = Math.floor(width * this.scale);
|
||||
const sh = Math.floor(height * this.scale);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthBlurProxyTexture.define(width, height);
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
|
||||
@@ -326,22 +333,24 @@ export class PostprocessingPass {
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
|
||||
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
|
||||
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
if (width !== w || height !== h) {
|
||||
const sw = Math.floor(width * this.scale);
|
||||
const sh = Math.floor(height * this.scale);
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.ssaoDepthTexture.define(width, height);
|
||||
this.ssaoDepthBlurProxyTexture.define(width, height);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,6 +466,14 @@ export class PostprocessingPass {
|
||||
}
|
||||
|
||||
if (props.occlusion.name === 'on') {
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
const sx = Math.floor(x * this.scale);
|
||||
const sy = Math.floor(y * this.scale);
|
||||
const sw = Math.floor(width * this.scale);
|
||||
const sh = Math.floor(height * this.scale);
|
||||
this.webgl.gl.viewport(sx, sy, sw, sh);
|
||||
this.webgl.gl.scissor(sx, sy, sw, sh);
|
||||
|
||||
this.ssaoFramebuffer.bind();
|
||||
this.ssaoRenderable.render();
|
||||
|
||||
@@ -465,6 +482,9 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoBlurSecondPassFramebuffer.bind();
|
||||
this.ssaoBlurSecondPassRenderable.render();
|
||||
|
||||
this.webgl.gl.viewport(x, y, width, height);
|
||||
this.webgl.gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
|
||||
@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
|
||||
}
|
||||
|
||||
/** Resize canvas to container element taking `devicePixelRatio` into account */
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
if (container !== document.body) {
|
||||
let bounds = container.getBoundingClientRect();
|
||||
width = bounds.right - bounds.left;
|
||||
height = bounds.bottom - bounds.top;
|
||||
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
|
||||
width = container.offsetWidth;
|
||||
height = container.offsetHeight;
|
||||
}
|
||||
setCanvasSize(canvas, width, height, scale);
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ function isIdentity(map: ArrayLike<number>, rowCount: number) {
|
||||
}
|
||||
|
||||
function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> {
|
||||
if (!c.isDefined || c.rowCount === 0) return c;
|
||||
if (c.rowCount === 0) return c;
|
||||
if (checkIdentity && isIdentity(map, c.rowCount)) return c;
|
||||
if (!!c.__array && typeof c.value(0) === typeof c.__array[0]) return arrayView(c, map);
|
||||
return viewFull(c, map);
|
||||
|
||||
@@ -75,6 +75,7 @@ export namespace BaseGeometry {
|
||||
export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {
|
||||
const opaque = props.alpha === undefined ? true : props.alpha === 1;
|
||||
return {
|
||||
disposed: false,
|
||||
visible: true,
|
||||
alphaFactor: 1,
|
||||
pickable: true,
|
||||
|
||||
@@ -89,7 +89,7 @@ export namespace Geometry {
|
||||
const [x, y, z] = geometry.gridDimension.ref.value;
|
||||
return x * y * z;
|
||||
case 'image': return 4;
|
||||
case 'texture-mesh': return geometry.vertexCount / 3;
|
||||
case 'texture-mesh': return geometry.vertexCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
|
||||
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { fillSerial } from '../../../mol-util/array';
|
||||
import { createEmptyClipping } from '../clipping-data';
|
||||
import { NullLocation } from '../../../mol-model/location';
|
||||
|
||||
@@ -34,22 +33,22 @@ export interface TextureMesh {
|
||||
groupCount: number,
|
||||
|
||||
readonly geoTextureDim: ValueCell<Vec2>,
|
||||
/** texture has vertex positions in XYZ and group id in W */
|
||||
readonly vertexGroupTexture: ValueCell<Texture>,
|
||||
readonly vertexTexture: ValueCell<Texture>,
|
||||
readonly groupTexture: ValueCell<Texture>,
|
||||
readonly normalTexture: ValueCell<Texture>,
|
||||
|
||||
readonly boundingSphere: Sphere3D
|
||||
}
|
||||
|
||||
export namespace TextureMesh {
|
||||
export function create(vertexCount: number, groupCount: number, vertexGroupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
|
||||
const width = vertexGroupTexture.getWidth();
|
||||
const height = vertexGroupTexture.getHeight();
|
||||
export function create(vertexCount: number, groupCount: number, vertexTexture: Texture, groupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
|
||||
const width = vertexTexture.getWidth();
|
||||
const height = vertexTexture.getHeight();
|
||||
if (textureMesh) {
|
||||
textureMesh.vertexCount = vertexCount;
|
||||
textureMesh.groupCount = groupCount;
|
||||
ValueCell.update(textureMesh.geoTextureDim, Vec2.set(textureMesh.geoTextureDim.ref.value, width, height));
|
||||
ValueCell.update(textureMesh.vertexGroupTexture, vertexGroupTexture);
|
||||
ValueCell.update(textureMesh.vertexTexture, vertexTexture);
|
||||
ValueCell.update(textureMesh.normalTexture, normalTexture);
|
||||
Sphere3D.copy(textureMesh.boundingSphere, boundingSphere);
|
||||
return textureMesh;
|
||||
@@ -59,7 +58,8 @@ export namespace TextureMesh {
|
||||
vertexCount,
|
||||
groupCount,
|
||||
geoTextureDim: ValueCell.create(Vec2.create(width, height)),
|
||||
vertexGroupTexture: ValueCell.create(vertexGroupTexture),
|
||||
vertexTexture: ValueCell.create(vertexTexture),
|
||||
groupTexture: ValueCell.create(groupTexture),
|
||||
normalTexture: ValueCell.create(normalTexture),
|
||||
boundingSphere: Sphere3D.clone(boundingSphere),
|
||||
};
|
||||
@@ -102,18 +102,17 @@ export namespace TextureMesh {
|
||||
const transparency = createEmptyTransparency();
|
||||
const clipping = createEmptyClipping();
|
||||
|
||||
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount / 3, groupCount, instanceCount };
|
||||
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount, groupCount, instanceCount };
|
||||
|
||||
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
uGeoTexDim: textureMesh.geoTextureDim,
|
||||
tPositionGroup: textureMesh.vertexGroupTexture,
|
||||
tPosition: textureMesh.vertexTexture,
|
||||
tGroup: textureMesh.groupTexture,
|
||||
tNormal: textureMesh.normalTexture,
|
||||
|
||||
// aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
|
||||
aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount))),
|
||||
boundingSphere: ValueCell.create(boundingSphere),
|
||||
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
|
||||
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
|
||||
@@ -148,11 +147,6 @@ export namespace TextureMesh {
|
||||
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')
|
||||
ValueCell.update(values.aGroup, fillSerial(new Float32Array(values.drawCount.ref.value)));
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
|
||||
|
||||
@@ -89,6 +89,7 @@ function createPoints() {
|
||||
uPointEdgeBleach: ValueCell.create(0.5),
|
||||
};
|
||||
const state: RenderableState = {
|
||||
disposed: false,
|
||||
visible: true,
|
||||
alphaFactor: 1,
|
||||
pickable: true,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
|
||||
@@ -18,41 +18,46 @@ import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
|
||||
import { isPowerOfTwo } from '../../../mol-math/misc';
|
||||
import quad_vert from '../../../mol-gl/shader/quad.vert';
|
||||
import reduction_frag from '../../../mol-gl/shader/histogram-pyramid/reduction.frag';
|
||||
import { isWebGL2 } from '../../webgl/compat';
|
||||
|
||||
const HistopyramidReductionSchema = {
|
||||
...QuadSchema,
|
||||
tInputLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tPreviousLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uSize: UniformSpec('f'),
|
||||
uTexSize: UniformSpec('f'),
|
||||
uFirst: UniformSpec('b'),
|
||||
};
|
||||
type HistopyramidReductionValues = Values<typeof HistopyramidReductionSchema>
|
||||
|
||||
const HistogramPyramidName = 'histogram-pyramid';
|
||||
|
||||
function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
|
||||
function getHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Texture, previousLevel: Texture): ComputeRenderable<HistopyramidReductionValues> {
|
||||
if (ctx.namedComputeRenderables[HistogramPyramidName]) {
|
||||
const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
|
||||
const v = ctx.namedComputeRenderables[HistogramPyramidName].values as HistopyramidReductionValues;
|
||||
|
||||
ValueCell.update(v.tPreviousLevel, initialTexture);
|
||||
ValueCell.update(v.tInputLevel, inputLevel);
|
||||
ValueCell.update(v.tPreviousLevel, previousLevel);
|
||||
|
||||
ctx.namedComputeRenderables[HistogramPyramidName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, initialTexture);
|
||||
ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, inputLevel, previousLevel);
|
||||
}
|
||||
return ctx.namedComputeRenderables[HistogramPyramidName];
|
||||
}
|
||||
|
||||
function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
|
||||
const values: Values<typeof HistopyramidReductionSchema> = {
|
||||
function createHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Texture, previousLevel: Texture) {
|
||||
const values: HistopyramidReductionValues = {
|
||||
...QuadValues,
|
||||
tPreviousLevel: ValueCell.create(initialTexture),
|
||||
tInputLevel: ValueCell.create(inputLevel),
|
||||
tPreviousLevel: ValueCell.create(previousLevel),
|
||||
uSize: ValueCell.create(0),
|
||||
uTexSize: ValueCell.create(0),
|
||||
uFirst: ValueCell.create(true),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidReductionSchema };
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag, {}, { 0: 'ivec4' });
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
@@ -64,11 +69,13 @@ function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
|
||||
let textureFramebuffer = LevelTexturesFramebuffers[level];
|
||||
const size = Math.pow(2, level);
|
||||
if (textureFramebuffer === undefined) {
|
||||
const texture = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
const texture = ctx.isWebGL2
|
||||
? getTexture(`level${level}`, ctx, 'image-int32', 'alpha', 'int', 'nearest')
|
||||
: getTexture(`level${level}`, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
texture.define(size, size);
|
||||
const framebuffer = getFramebuffer(`level${level}`, ctx);
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
textureFramebuffer = { texture, framebuffer };
|
||||
textureFramebuffer.texture.define(size, size);
|
||||
LevelTexturesFramebuffers[level] = textureFramebuffer;
|
||||
}
|
||||
return textureFramebuffer;
|
||||
@@ -122,20 +129,29 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
// This part set the levels
|
||||
const levels = Math.ceil(Math.log(w) / Math.log(2));
|
||||
const maxSize = Math.pow(2, levels);
|
||||
// console.log('levels', levels, 'maxSize', maxSize, 'input', w);
|
||||
const maxSizeX = Math.pow(2, levels);
|
||||
const maxSizeY = Math.pow(2, levels - 1);
|
||||
// console.log('levels', levels, 'maxSize', maxSize, [maxSizeX, maxSizeY], 'input', w);
|
||||
|
||||
const pyramidTex = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
pyramidTex.define(maxSize, maxSize);
|
||||
const pyramidTex = ctx.isWebGL2
|
||||
? getTexture('pyramid', ctx, 'image-int32', 'alpha', 'int', 'nearest')
|
||||
: getTexture('pyramid', ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
pyramidTex.define(maxSizeX, maxSizeY);
|
||||
|
||||
const framebuffer = getFramebuffer('pyramid', ctx);
|
||||
pyramidTex.attachFramebuffer(framebuffer, 0);
|
||||
gl.viewport(0, 0, maxSize, maxSize);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
gl.viewport(0, 0, maxSizeX, maxSizeY);
|
||||
if (isWebGL2(gl)) {
|
||||
gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
|
||||
} else {
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
const levelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i));
|
||||
|
||||
const renderable = getHistopyramidReductionRenderable(ctx, inputTexture);
|
||||
const renderable = getHistopyramidReductionRenderable(ctx, inputTexture, levelTexturesFramebuffers[0].texture);
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
setRenderingDefaults(ctx);
|
||||
|
||||
@@ -146,7 +162,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
tf.framebuffer.bind();
|
||||
|
||||
const size = Math.pow(2, currLevel);
|
||||
// console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
|
||||
// console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i);
|
||||
|
||||
ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
|
||||
ValueCell.update(renderable.values.uTexSize, size);
|
||||
@@ -158,7 +174,11 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
gl.viewport(0, 0, size, size);
|
||||
gl.scissor(0, 0, size, size);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
if (isWebGL2(gl)) {
|
||||
gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
|
||||
} else {
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
|
||||
renderable.render();
|
||||
|
||||
@@ -179,7 +199,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
|
||||
const count = Math.max(1, getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture));
|
||||
const height = Math.ceil(count / Math.pow(2, levels));
|
||||
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height);
|
||||
// console.log('height', height, 'finalCount', finalCount, 'scale', scale);
|
||||
// console.log('height', height, 'finalCount', count, 'scale', scale);
|
||||
|
||||
return { pyramidTex, count, height, levels, scale };
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec } from '../../renderable/schema';
|
||||
@@ -15,17 +15,19 @@ import { decodeFloatRGB } from '../../../mol-util/float-packing';
|
||||
import { QuadSchema, QuadValues } from '../util';
|
||||
import quad_vert from '../../../mol-gl/shader/quad.vert';
|
||||
import sum_frag from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
|
||||
import { isWebGL2 } from '../../webgl/compat';
|
||||
|
||||
const HistopyramidSumSchema = {
|
||||
...QuadSchema,
|
||||
tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
};
|
||||
type HistopyramidSumValues = Values<typeof HistopyramidSumSchema>
|
||||
|
||||
const HistopyramidSumName = 'histopyramid-sum';
|
||||
|
||||
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture): ComputeRenderable<HistopyramidSumValues> {
|
||||
if (ctx.namedComputeRenderables[HistopyramidSumName]) {
|
||||
const v = ctx.namedComputeRenderables[HistopyramidSumName].values;
|
||||
const v = ctx.namedComputeRenderables[HistopyramidSumName].values as HistopyramidSumValues;
|
||||
|
||||
ValueCell.update(v.tTexture, texture);
|
||||
|
||||
@@ -37,13 +39,13 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
}
|
||||
|
||||
function createHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
const values: Values<typeof HistopyramidSumSchema> = {
|
||||
const values: HistopyramidSumValues = {
|
||||
...QuadValues,
|
||||
tTexture: ValueCell.create(texture),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidSumSchema };
|
||||
const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
|
||||
const shaderCode = ShaderCode('sum', quad_vert, sum_frag, {}, { 0: 'ivec4' });
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
@@ -60,7 +62,9 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
const sumArray = new Uint8Array(4);
|
||||
const sumBytes = new Uint8Array(4);
|
||||
const sumInts = new Int32Array(4);
|
||||
|
||||
export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
|
||||
const { gl, resources } = ctx;
|
||||
|
||||
@@ -73,7 +77,9 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
|
||||
const framebuffer = ctx.namedFramebuffers[HistopyramidSumName];
|
||||
|
||||
if (!ctx.namedTextures[HistopyramidSumName]) {
|
||||
ctx.namedTextures[HistopyramidSumName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
ctx.namedTextures[HistopyramidSumName] = isWebGL2(gl)
|
||||
? resources.texture('image-int32', 'rgba', 'int', 'nearest')
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
ctx.namedTextures[HistopyramidSumName].define(1, 1);
|
||||
}
|
||||
const sumTexture = ctx.namedTextures[HistopyramidSumName];
|
||||
@@ -84,8 +90,11 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
|
||||
gl.viewport(0, 0, 1, 1);
|
||||
renderable.render();
|
||||
gl.finish();
|
||||
ctx.readPixels(0, 0, 1, 1, sumArray);
|
||||
|
||||
ctx.readPixels(0, 0, 1, 1, isWebGL2(gl) ? sumInts : sumBytes);
|
||||
ctx.unbindFramebuffer();
|
||||
|
||||
return decodeFloatRGB(sumArray[0], sumArray[1], sumArray[2]);
|
||||
return isWebGL2(gl)
|
||||
? sumInts[0]
|
||||
: decodeFloatRGB(sumBytes[0], sumBytes[1], sumBytes[2]);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
|
||||
@@ -29,12 +29,13 @@ const ActiveVoxelsSchema = {
|
||||
|
||||
uScale: UniformSpec('v2'),
|
||||
};
|
||||
type ActiveVoxelsValues = Values<typeof ActiveVoxelsSchema>
|
||||
|
||||
const ActiveVoxelsName = 'active-voxels';
|
||||
|
||||
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
|
||||
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2): ComputeRenderable<ActiveVoxelsValues> {
|
||||
if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
|
||||
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values;
|
||||
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values as ActiveVoxelsValues;
|
||||
|
||||
ValueCell.update(v.uQuadScale, scale);
|
||||
ValueCell.update(v.tVolumeData, volumeData);
|
||||
@@ -51,7 +52,7 @@ function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridD
|
||||
}
|
||||
|
||||
function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
|
||||
const values: Values<typeof ActiveVoxelsSchema> = {
|
||||
const values: ActiveVoxelsValues = {
|
||||
...QuadValues,
|
||||
tTriCount: ValueCell.create(getTriCount()),
|
||||
|
||||
|
||||
@@ -4,19 +4,21 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
|
||||
import { Values, TextureSpec, UniformSpec, DefineSpec } from '../../renderable/schema';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { ShaderCode } from '../../../mol-gl/shader-code';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Vec3, Vec2, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { QuadSchema, QuadValues } from '../util';
|
||||
import { HistogramPyramid } from '../histogram-pyramid/reduction';
|
||||
import { createHistogramPyramid, HistogramPyramid } from '../histogram-pyramid/reduction';
|
||||
import { getTriIndices } from './tables';
|
||||
import quad_vert from '../../../mol-gl/shader/quad.vert';
|
||||
import isosurface_frag from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
|
||||
import { calcActiveVoxels } from './active-voxels';
|
||||
import { isWebGL2 } from '../../webgl/compat';
|
||||
|
||||
const IsosurfaceSchema = {
|
||||
...QuadSchema,
|
||||
@@ -34,15 +36,17 @@ const IsosurfaceSchema = {
|
||||
uGridDim: UniformSpec('v3'),
|
||||
uGridTexDim: UniformSpec('v3'),
|
||||
uGridTransform: UniformSpec('m4'),
|
||||
|
||||
uScale: UniformSpec('v2'),
|
||||
|
||||
dPackedGroup: DefineSpec('boolean')
|
||||
};
|
||||
type IsosurfaceValues = Values<typeof IsosurfaceSchema>
|
||||
|
||||
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) {
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values;
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
|
||||
|
||||
ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
|
||||
ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
|
||||
@@ -58,16 +62,18 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
ValueCell.update(v.uGridTransform, transform);
|
||||
ValueCell.update(v.uScale, scale);
|
||||
|
||||
ValueCell.update(v.dPackedGroup, packedGroup);
|
||||
|
||||
ctx.namedComputeRenderables[IsosurfaceName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
|
||||
}
|
||||
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) {
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean) {
|
||||
// console.log('uSize', Math.pow(2, levels))
|
||||
const values: Values<typeof IsosurfaceSchema> = {
|
||||
const values: IsosurfaceValues = {
|
||||
...QuadValues,
|
||||
tTriIndices: ValueCell.create(getTriIndices()),
|
||||
|
||||
@@ -84,6 +90,8 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
uGridTransform: ValueCell.create(transform),
|
||||
uScale: ValueCell.create(scale),
|
||||
|
||||
dPackedGroup: ValueCell.create(packedGroup)
|
||||
};
|
||||
|
||||
const schema = { ...IsosurfaceSchema };
|
||||
@@ -104,65 +112,72 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, vertexGroupTexture?: Texture, normalTexture?: Texture) {
|
||||
const { gl, resources } = ctx;
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
const { gl, resources, extensions } = ctx;
|
||||
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
|
||||
const width = pyramidTex.getWidth();
|
||||
|
||||
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
|
||||
// console.log('iso volumeData', volumeData)
|
||||
// console.log('width', width, 'height', height);
|
||||
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim);
|
||||
// console.log('iso volumeData', volumeData);
|
||||
|
||||
if (!ctx.namedFramebuffers[IsosurfaceName]) {
|
||||
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
|
||||
}
|
||||
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
|
||||
|
||||
if (!vertexGroupTexture) {
|
||||
vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
vertexGroupTexture.define(width, height);
|
||||
if (isWebGL2(gl)) {
|
||||
if (!vertexTexture) {
|
||||
vertexTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
|
||||
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
|
||||
if (!normalTexture) {
|
||||
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
if (!groupTexture) {
|
||||
groupTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
}
|
||||
|
||||
if (!normalTexture) {
|
||||
normalTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
|
||||
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
} else {
|
||||
// in webgl1 drawbuffers must be in the same format for some reason
|
||||
// this is quite wasteful but good enough for medium size meshes
|
||||
|
||||
if (!vertexTexture) {
|
||||
vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
|
||||
if (!groupTexture) {
|
||||
groupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
|
||||
if (!normalTexture) {
|
||||
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
}
|
||||
}
|
||||
|
||||
vertexTexture.define(width, height);
|
||||
groupTexture.define(width, height);
|
||||
normalTexture.define(width, height);
|
||||
|
||||
// const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// infoTex.define(pyramidTex.width, pyramidTex.height)
|
||||
vertexTexture.attachFramebuffer(framebuffer, 0);
|
||||
groupTexture.attachFramebuffer(framebuffer, 1);
|
||||
normalTexture.attachFramebuffer(framebuffer, 2);
|
||||
|
||||
// const pointTexA = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// pointTexA.define(pyramidTex.width, pyramidTex.height)
|
||||
|
||||
// const pointTexB = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// pointTexB.define(pyramidTex.width, pyramidTex.height)
|
||||
|
||||
// const coordTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// coordTex.define(pyramidTex.width, pyramidTex.height)
|
||||
|
||||
// const indexTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// indexTex.define(pyramidTex.width, pyramidTex.height)
|
||||
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
|
||||
vertexGroupTexture.attachFramebuffer(framebuffer, 0);
|
||||
normalTexture.attachFramebuffer(framebuffer, 1);
|
||||
// infoTex.attachFramebuffer(framebuffer, 1)
|
||||
// pointTexA.attachFramebuffer(framebuffer, 2)
|
||||
// pointTexB.attachFramebuffer(framebuffer, 3)
|
||||
// coordTex.attachFramebuffer(framebuffer, 4)
|
||||
// indexTex.attachFramebuffer(framebuffer, 5)
|
||||
|
||||
const { drawBuffers } = ctx.extensions;
|
||||
if (!drawBuffers) throw new Error('need WebGL draw buffers');
|
||||
|
||||
framebuffer.bind();
|
||||
drawBuffers.drawBuffers([
|
||||
drawBuffers.COLOR_ATTACHMENT0,
|
||||
drawBuffers.COLOR_ATTACHMENT1,
|
||||
// drawBuffers.COLOR_ATTACHMENT2,
|
||||
// drawBuffers.COLOR_ATTACHMENT3,
|
||||
// drawBuffers.COLOR_ATTACHMENT4,
|
||||
// drawBuffers.COLOR_ATTACHMENT5
|
||||
drawBuffers.COLOR_ATTACHMENT2,
|
||||
]);
|
||||
|
||||
setRenderingDefaults(ctx);
|
||||
@@ -172,45 +187,26 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
|
||||
gl.flush();
|
||||
|
||||
// const vgt = readTexture(ctx, vertexGroupTexture, pyramidTex.width, height)
|
||||
// console.log('vertexGroupTexture', vgt.array.subarray(0, 4 * count))
|
||||
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
|
||||
}
|
||||
|
||||
// const vt = readTexture(ctx, verticesTex, pyramidTex.width, height)
|
||||
// console.log('vt', vt)
|
||||
// const vertices = new Float32Array(3 * compacted.count)
|
||||
// for (let i = 0; i < compacted.count; ++i) {
|
||||
// vertices[i * 3] = vt.array[i * 4]
|
||||
// vertices[i * 3 + 1] = vt.array[i * 4 + 1]
|
||||
// vertices[i * 3 + 2] = vt.array[i * 4 + 2]
|
||||
// }
|
||||
// console.log('vertices', vertices)
|
||||
//
|
||||
|
||||
// const it = readTexture(ctx, infoTex, pyramidTex.width, height)
|
||||
// console.log('info', it.array.subarray(0, 4 * compacted.count))
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
|
||||
// const pat = readTexture(ctx, pointTexA, pyramidTex.width, height)
|
||||
// console.log('point a', pat.array.subarray(0, 4 * compacted.count))
|
||||
// console.time('createHistogramPyramid');
|
||||
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createHistogramPyramid');
|
||||
|
||||
// const pbt = readTexture(ctx, pointTexB, pyramidTex.width, height)
|
||||
// console.log('point b', pbt.array.subarray(0, 4 * compacted.count))
|
||||
// console.time('createIsosurfaceBuffers');
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
|
||||
// const ct = readTexture(ctx, coordTex, pyramidTex.width, height)
|
||||
// console.log('coord', ct.array.subarray(0, 4 * compacted.count))
|
||||
|
||||
// const idxt = readTexture(ctx, indexTex, pyramidTex.width, height)
|
||||
// console.log('index', idxt.array.subarray(0, 4 * compacted.count))
|
||||
|
||||
// const { field, idField } = await fieldFromTexture2d(ctx, volumeData, gridDimensions)
|
||||
// console.log({ field, idField })
|
||||
|
||||
// const valuesA = new Float32Array(compacted.count)
|
||||
// const valuesB = new Float32Array(compacted.count)
|
||||
// for (let i = 0; i < compacted.count; ++i) {
|
||||
// valuesA[i] = field.space.get(field.data, pat.array[i * 4], pat.array[i * 4 + 1], pat.array[i * 4 + 2])
|
||||
// valuesB[i] = field.space.get(field.data, pbt.array[i * 4], pbt.array[i * 4 + 1], pbt.array[i * 4 + 2])
|
||||
// }
|
||||
// console.log('valuesA', valuesA)
|
||||
// console.log('valuesB', valuesB)
|
||||
|
||||
return { vertexGroupTexture, normalTexture, vertexCount: count };
|
||||
return gv;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { Textures } from './webgl/texture';
|
||||
const getNextRenderableId = idFactory();
|
||||
|
||||
export type RenderableState = {
|
||||
disposed: boolean
|
||||
visible: boolean
|
||||
alphaFactor: number
|
||||
pickable: boolean
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
|
||||
export const CylindersSchema = {
|
||||
...BaseSchema,
|
||||
...SizeSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aStart: AttributeSpec('float32', 3, 0),
|
||||
aEnd: AttributeSpec('float32', 3, 0),
|
||||
aMapping: AttributeSpec('float32', 3, 0),
|
||||
|
||||
@@ -7,59 +7,20 @@
|
||||
import { Renderable, RenderableState, createRenderable } from '../renderable';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { createGraphicsRenderItem } from '../webgl/render-item';
|
||||
import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ValueSpec, ElementsSpec, DefineSpec, InternalValues, GlobalTextureSchema } from './schema';
|
||||
import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ElementsSpec, DefineSpec, InternalValues, GlobalTextureSchema, BaseSchema } from './schema';
|
||||
import { DirectVolumeShaderCode } from '../shader-code';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
|
||||
export const DirectVolumeSchema = {
|
||||
uColor: UniformSpec('v3'),
|
||||
uColorTexDim: UniformSpec('v2'),
|
||||
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
|
||||
|
||||
uMarkerTexDim: UniformSpec('v2'),
|
||||
tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
|
||||
|
||||
uOverpaintTexDim: UniformSpec('v2'),
|
||||
tOverpaint: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
|
||||
dOverpaint: DefineSpec('boolean'),
|
||||
|
||||
uTransparencyTexDim: UniformSpec('v2'),
|
||||
tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
|
||||
dTransparency: DefineSpec('boolean'),
|
||||
transparencyAverage: ValueSpec('number'),
|
||||
|
||||
dClipObjectCount: DefineSpec('number'),
|
||||
dClipVariant: DefineSpec('string', ['instance', 'pixel']),
|
||||
uClippingTexDim: UniformSpec('v2'),
|
||||
tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
|
||||
dClipping: DefineSpec('boolean'),
|
||||
|
||||
uVertexCount: UniformSpec('i'),
|
||||
uInstanceCount: UniformSpec('i'),
|
||||
uGroupCount: UniformSpec('i'),
|
||||
uInvariantBoundingSphere: UniformSpec('v4'),
|
||||
|
||||
aInstance: AttributeSpec('float32', 1, 1),
|
||||
aTransform: AttributeSpec('float32', 16, 1),
|
||||
|
||||
drawCount: ValueSpec('number'),
|
||||
instanceCount: ValueSpec('number'),
|
||||
|
||||
alpha: ValueSpec('number'),
|
||||
|
||||
matrix: ValueSpec('m4'),
|
||||
transform: ValueSpec('float32'),
|
||||
extraTransform: ValueSpec('float32'),
|
||||
hasReflection: ValueSpec('boolean'),
|
||||
|
||||
boundingSphere: ValueSpec('sphere'),
|
||||
invariantBoundingSphere: ValueSpec('sphere'),
|
||||
...BaseSchema,
|
||||
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
uAlpha: UniformSpec('f'),
|
||||
uColor: UniformSpec('v3'),
|
||||
uColorTexDim: UniformSpec('v2'),
|
||||
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
|
||||
|
||||
uIsoValue: UniformSpec('v2'),
|
||||
uBboxMin: UniformSpec('v3'),
|
||||
|
||||
@@ -15,9 +15,9 @@ import { InterpolationTypeNames } from '../../mol-geo/geometry/image/image';
|
||||
export const ImageSchema = {
|
||||
...BaseSchema,
|
||||
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
aUv: AttributeSpec('float32', 2, 0),
|
||||
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
uImageTexDim: UniformSpec('v2'),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { LinesShaderCode } from '../shader-code';
|
||||
export const LinesSchema = {
|
||||
...BaseSchema,
|
||||
...SizeSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aMapping: AttributeSpec('float32', 2, 0),
|
||||
aStart: AttributeSpec('float32', 3, 0),
|
||||
aEnd: AttributeSpec('float32', 3, 0),
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ValueCell } from '../../mol-util';
|
||||
|
||||
export const MeshSchema = {
|
||||
...BaseSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
aNormal: AttributeSpec('float32', 3, 0),
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
|
||||
export const PointsSchema = {
|
||||
...BaseSchema,
|
||||
...SizeSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
dPointSizeAttenuation: DefineSpec('boolean'),
|
||||
dPointFilledCircle: DefineSpec('boolean'),
|
||||
|
||||
@@ -159,6 +159,8 @@ export const GlobalUniformSchema = {
|
||||
uHighlightColor: UniformSpec('v3'),
|
||||
uSelectColor: UniformSpec('v3'),
|
||||
|
||||
uXrayEdgeFalloff: UniformSpec('f'),
|
||||
|
||||
uRenderWboit: UniformSpec('b'),
|
||||
} as const;
|
||||
export type GlobalUniformSchema = typeof GlobalUniformSchema
|
||||
@@ -240,7 +242,6 @@ export const BaseSchema = {
|
||||
...ClippingSchema,
|
||||
|
||||
aInstance: AttributeSpec('float32', 1, 1),
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
/**
|
||||
* final per-instance transform calculated for instance `i` as
|
||||
* `aTransform[i] = matrix * transform[i] * extraTransform[i]`
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
|
||||
export const SpheresSchema = {
|
||||
...BaseSchema,
|
||||
...SizeSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
aMapping: AttributeSpec('float32', 2, 0),
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
|
||||
export const TextSchema = {
|
||||
...BaseSchema,
|
||||
...SizeSchema,
|
||||
aGroup: AttributeSpec('float32', 1, 0),
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
aMapping: AttributeSpec('float32', 2, 0),
|
||||
aDepth: AttributeSpec('float32', 1, 0),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -13,11 +13,10 @@ import { ValueCell } from '../../mol-util';
|
||||
|
||||
export const TextureMeshSchema = {
|
||||
...BaseSchema,
|
||||
|
||||
uGeoTexDim: UniformSpec('v2'),
|
||||
/** texture has vertex positions in XYZ and group id in W */
|
||||
tPositionGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tNormal: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'),
|
||||
tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'),
|
||||
tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'),
|
||||
|
||||
dFlatShaded: DefineSpec('boolean'),
|
||||
dDoubleSided: DefineSpec('boolean'),
|
||||
|
||||
@@ -17,7 +17,7 @@ export function calculateTextureInfo (n: number, itemSize: number) {
|
||||
return { width, height, length: width * height * itemSize };
|
||||
}
|
||||
|
||||
export interface TextureImage<T extends Uint8Array | Float32Array> {
|
||||
export interface TextureImage<T extends Uint8Array | Float32Array | Int32Array> {
|
||||
readonly array: T
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
|
||||
@@ -77,6 +77,8 @@ export const RendererParams = {
|
||||
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
|
||||
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
|
||||
|
||||
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
|
||||
|
||||
style: PD.MappedStatic('matte', {
|
||||
custom: PD.Group({
|
||||
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
@@ -257,13 +259,15 @@ namespace Renderer {
|
||||
|
||||
uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
|
||||
uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
|
||||
|
||||
uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff),
|
||||
};
|
||||
const globalUniformList = Object.entries(globalUniforms);
|
||||
|
||||
let globalUniformsNeedUpdate = true;
|
||||
|
||||
const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant) => {
|
||||
if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
|
||||
if (r.state.disposed || !r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -607,6 +611,11 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
|
||||
}
|
||||
|
||||
if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) {
|
||||
p.xrayEdgeFalloff = props.xrayEdgeFalloff;
|
||||
ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
|
||||
}
|
||||
|
||||
if (props.style !== undefined) {
|
||||
p.style = props.style;
|
||||
Object.assign(style, getStyle(props.style));
|
||||
|
||||
@@ -23,12 +23,16 @@ export interface ShaderExtensions {
|
||||
readonly shaderTextureLod?: ShaderExtensionsValue
|
||||
}
|
||||
|
||||
type FragOutTypes = { [k in number]: 'vec4' | 'ivec4' }
|
||||
|
||||
export interface ShaderCode {
|
||||
readonly id: number
|
||||
readonly name: string
|
||||
readonly vert: string
|
||||
readonly frag: string
|
||||
readonly extensions: ShaderExtensions
|
||||
/** Fragment shader output type only applicable for webgl2 */
|
||||
readonly outTypes: FragOutTypes
|
||||
}
|
||||
|
||||
import apply_fog from './shader/chunks/apply-fog.glsl';
|
||||
@@ -117,8 +121,8 @@ function addIncludes(text: string) {
|
||||
.replace(reMultipleLinebreaks, '\n');
|
||||
}
|
||||
|
||||
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
|
||||
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions };
|
||||
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}, outTypes: FragOutTypes = {}): ShaderCode {
|
||||
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions, outTypes };
|
||||
}
|
||||
|
||||
// Note: `drawBuffers` need to be 'optional' for wboit
|
||||
@@ -226,8 +230,6 @@ const glsl300VertPrefix = `#version 300 es
|
||||
`;
|
||||
|
||||
const glsl300FragPrefixCommon = `
|
||||
layout(location = 0) out highp vec4 out_FragData0;
|
||||
|
||||
#define varying in
|
||||
#define texture2D texture
|
||||
#define texture2DLodEXT textureLod
|
||||
@@ -238,8 +240,12 @@ layout(location = 0) out highp vec4 out_FragData0;
|
||||
#define depthTextureSupport
|
||||
`;
|
||||
|
||||
function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
|
||||
const prefix = [ '#version 300 es' ];
|
||||
function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions, outTypes: FragOutTypes) {
|
||||
const prefix = [
|
||||
'#version 300 es',
|
||||
`layout(location = 0) out highp ${outTypes[0] || 'vec4'} out_FragData0;`
|
||||
];
|
||||
|
||||
if (shaderExtensions.standardDerivatives) {
|
||||
prefix.push('#define enabledStandardDerivatives');
|
||||
}
|
||||
@@ -250,7 +256,7 @@ function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExten
|
||||
prefix.push('#define requiredDrawBuffers');
|
||||
const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number;
|
||||
for (let i = 1, il = maxDrawBuffers; i < il; ++i) {
|
||||
prefix.push(`layout(location = ${i}) out highp vec4 out_FragData${i};`);
|
||||
prefix.push(`layout(location = ${i}) out highp ${outTypes[i] || 'vec4'} out_FragData${i};`);
|
||||
}
|
||||
}
|
||||
if (shaderExtensions.shaderTextureLod) {
|
||||
@@ -268,7 +274,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
|
||||
const header = getDefinesCode(defines);
|
||||
const vertPrefix = isWebGL2(gl) ? glsl300VertPrefix : '';
|
||||
const fragPrefix = isWebGL2(gl)
|
||||
? getGlsl300FragPrefix(gl, extensions, shaders.extensions)
|
||||
? getGlsl300FragPrefix(gl, extensions, shaders.extensions, shaders.outTypes)
|
||||
: getGlsl100FragPrefix(extensions, shaders.extensions);
|
||||
const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag;
|
||||
return {
|
||||
@@ -276,6 +282,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
|
||||
name: shaders.name,
|
||||
vert: `${vertPrefix}${header}${shaders.vert}`,
|
||||
frag: `${fragPrefix}${header}${frag}`,
|
||||
extensions: shaders.extensions
|
||||
extensions: shaders.extensions,
|
||||
outTypes: shaders.outTypes
|
||||
};
|
||||
}
|
||||
@@ -49,6 +49,6 @@ vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffu
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
|
||||
#ifdef dXrayShaded
|
||||
gl_FragColor.a *= 1.0 - max(0.001, abs(dot(normal, vec3(0, 0, 1))));
|
||||
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0, 0, 1))), uXrayEdgeFalloff);
|
||||
#endif
|
||||
`;
|
||||
@@ -9,9 +9,9 @@ export default `
|
||||
#elif defined(dColorType_groupInstance)
|
||||
vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_vertex)
|
||||
vColor.rgb = readFromTexture(tColor, aVertex, uColorTexDim).rgb;
|
||||
vColor.rgb = readFromTexture(tColor, VertexID, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_vertexInstance)
|
||||
vColor.rgb = readFromTexture(tColor, aInstance * float(uVertexCount) + aVertex, uColorTexDim).rgb;
|
||||
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export default `
|
||||
#ifdef dGeoTexture
|
||||
// aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
|
||||
float group = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).w;
|
||||
float group = decodeFloatRGB(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
|
||||
#else
|
||||
float group = aGroup;
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@ export default `
|
||||
mat4 model = uModel * aTransform;
|
||||
mat4 modelView = uView * model;
|
||||
#ifdef dGeoTexture
|
||||
vec3 position = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).xyz;
|
||||
vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 position = aPosition;
|
||||
#endif
|
||||
|
||||
@@ -11,14 +11,6 @@ export default `
|
||||
uniform sampler2D tColor;
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_vertex) || defined(dColorType_vertexInstance)
|
||||
#if __VERSION__ == 100
|
||||
attribute float aVertex;
|
||||
#else
|
||||
#define aVertex float(gl_VertexID)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
varying vec4 vOverpaint;
|
||||
uniform vec2 uOverpaintTexDim;
|
||||
|
||||
@@ -49,5 +49,7 @@ uniform bool uInteriorColorFlag;
|
||||
uniform vec3 uInteriorColor;
|
||||
bool interior;
|
||||
|
||||
uniform float uXrayEdgeFalloff;
|
||||
|
||||
uniform mat4 uProjection;
|
||||
`;
|
||||
@@ -36,4 +36,17 @@ uniform sampler2D tMarker;
|
||||
|
||||
varying vec3 vModelPosition;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
#if __VERSION__ == 100
|
||||
attribute float aVertex;
|
||||
#define VertexID int(aVertex)
|
||||
#else
|
||||
// not using gl_VertexID but aVertex to ensure there is an active attribute with divisor 0
|
||||
// since FF 85 this is not needed anymore but lets keep it for backwards compatibility
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1679693
|
||||
// see also note in src/mol-gl/webgl/render-item.ts
|
||||
attribute float aVertex;
|
||||
#define VertexID int(aVertex)
|
||||
// #define VertexID gl_VertexID
|
||||
#endif
|
||||
`;
|
||||
@@ -28,6 +28,7 @@ export default `
|
||||
float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
|
||||
vec2 ivec2Div(const in vec2 a, const in vec2 b) { return vec2(ivec2(a) / ivec2(b)); }
|
||||
float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
|
||||
int imod(const in int a, const in int b) { return a - b * (a / b); }
|
||||
|
||||
float pow2(const in float x) { return x * x; }
|
||||
|
||||
|
||||
@@ -5,10 +5,17 @@
|
||||
*/
|
||||
|
||||
export default `
|
||||
vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) {
|
||||
vec4 readFromTexture(const in sampler2D tex, const in float i, const in vec2 dim) {
|
||||
float x = intMod(i, dim.x);
|
||||
float y = floor(intDiv(i, dim.x));
|
||||
vec2 uv = (vec2(x, y) + 0.5) / dim;
|
||||
return texture2D(tex, uv);
|
||||
}
|
||||
|
||||
vec4 readFromTexture(const in sampler2D tex, const in int i, const in vec2 dim) {
|
||||
int x = imod(i, int(dim.x));
|
||||
int y = i / int(dim.x);
|
||||
vec2 uv = (vec2(x, y) + 0.5) / dim;
|
||||
return texture2D(tex, uv);
|
||||
}
|
||||
`;
|
||||
@@ -1,35 +1,59 @@
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
// input texture (previous level used to evaluate the new level)
|
||||
uniform sampler2D tPreviousLevel;
|
||||
uniform sampler2D tInputLevel;
|
||||
|
||||
// previous level used to evaluate the new level
|
||||
#if __VERSION__ == 100
|
||||
uniform sampler2D tPreviousLevel;
|
||||
#else
|
||||
precision highp isampler2D;
|
||||
uniform isampler2D tPreviousLevel;
|
||||
#endif
|
||||
|
||||
// inverted size of the previous level texture.
|
||||
uniform float uSize;
|
||||
uniform float uTexSize;
|
||||
uniform bool uFirst;
|
||||
|
||||
#include common
|
||||
|
||||
void main(void) {
|
||||
float k = 0.5 * uSize;
|
||||
vec2 position = floor((gl_FragCoord.xy / uTexSize) / uSize) * uSize;
|
||||
float a, b, c, d;
|
||||
|
||||
if (uFirst) {
|
||||
a = texture2D(tPreviousLevel, position).r * 255.0;
|
||||
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r * 255.0;
|
||||
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r * 255.0;
|
||||
d = texture2D(tPreviousLevel, position + vec2(k, k)).r * 255.0;
|
||||
} else {
|
||||
a = texture2D(tPreviousLevel, position).r;
|
||||
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r;
|
||||
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r;
|
||||
d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
|
||||
}
|
||||
#if __VERSION__ == 100
|
||||
float a, b, c, d;
|
||||
|
||||
gl_FragColor.a = a;
|
||||
gl_FragColor.b = a + b;
|
||||
gl_FragColor.g = gl_FragColor.b + c;
|
||||
gl_FragColor.r = gl_FragColor.g + d;
|
||||
if (uFirst) {
|
||||
a = texture2D(tInputLevel, position).r * 255.0;
|
||||
b = texture2D(tInputLevel, position + vec2(k, 0.0)).r * 255.0;
|
||||
c = texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0;
|
||||
d = texture2D(tInputLevel, position + vec2(k, k)).r * 255.0;
|
||||
} else {
|
||||
a = decodeFloatRGB(texture2D(tPreviousLevel, position).rgb);
|
||||
b = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
|
||||
c = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
|
||||
d = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
|
||||
}
|
||||
gl_FragColor = vec4(encodeFloatRGB(a + b + c + d), 1.0);
|
||||
#else
|
||||
int a, b, c, d;
|
||||
|
||||
if (uFirst) {
|
||||
a = int(texture2D(tInputLevel, position).r * 255.0);
|
||||
b = int(texture2D(tInputLevel, position + vec2(k, 0.0)).r * 255.0);
|
||||
c = int(texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0);
|
||||
d = int(texture2D(tInputLevel, position + vec2(k, k)).r * 255.0);
|
||||
} else {
|
||||
a = texture2D(tPreviousLevel, position).r;
|
||||
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r;
|
||||
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r;
|
||||
d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
|
||||
}
|
||||
gl_FragColor = ivec4(a + b + c + d);
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
@@ -1,18 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
precision highp int;
|
||||
|
||||
uniform sampler2D tTexture;
|
||||
|
||||
#include common
|
||||
#if __VERSION__ == 100
|
||||
precision highp sampler2D;
|
||||
uniform sampler2D tTexture;
|
||||
#else
|
||||
precision highp isampler2D;
|
||||
uniform isampler2D tTexture;
|
||||
#endif
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = vec4(encodeFloatRGB(texture2D(tTexture, vec2(0.5)).r), 1.0);
|
||||
#if __VERSION__ == 100
|
||||
gl_FragColor = texture2D(tTexture, vec2(0.5));
|
||||
#else
|
||||
gl_FragColor = ivec4(texture2D(tTexture, vec2(0.5)).r);
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
@@ -3,7 +3,13 @@ precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tActiveVoxelsPyramid;
|
||||
#if __VERSION__ == 100
|
||||
uniform sampler2D tActiveVoxelsPyramid;
|
||||
#else
|
||||
precision highp isampler2D;
|
||||
uniform isampler2D tActiveVoxelsPyramid;
|
||||
#endif
|
||||
|
||||
uniform sampler2D tActiveVoxelsBase;
|
||||
uniform sampler2D tVolumeData;
|
||||
uniform sampler2D tTriIndices;
|
||||
@@ -52,65 +58,114 @@ vec4 voxel(vec3 pos) {
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
vec4 voxel2(vec3 pos) {
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(vec2(2.0), 1.0));
|
||||
vec4 voxelPadded(vec3 pos) {
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(vec2(2.0), 1.0)); // remove xy padding
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
int idot2(const in ivec2 a, const in ivec2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
int idot4(const in ivec4 a, const in ivec4 b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
|
||||
#if __VERSION__ == 100
|
||||
int pyramidVoxel(vec2 pos) {
|
||||
return int(decodeFloatRGB(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
|
||||
}
|
||||
#else
|
||||
int pyramidVoxel(vec2 pos) {
|
||||
return texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).r;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 baseVoxel(vec2 pos) {
|
||||
return texture2D(tActiveVoxelsBase, pos / uSize);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
// get 1D index
|
||||
float vI = dot(floor(uSize * (gl_FragCoord.xy / uSize)), vec2(1.0, uSize));
|
||||
int vI = int(gl_FragCoord.x) + int(gl_FragCoord.y) * int(uSize);
|
||||
|
||||
// ignore 1D indices outside of the grid
|
||||
if(vI >= uCount) discard;
|
||||
if(vI >= int(uCount)) discard;
|
||||
|
||||
float offset = uSize - 2.;
|
||||
float k = 1. / uSize;
|
||||
ivec2 offset = ivec2(int(uSize) - 2, 0);
|
||||
|
||||
vec2 relativePosition = k * vec2(offset, 0.);
|
||||
vec4 partialSums = texture2D(tActiveVoxelsPyramid, relativePosition);
|
||||
float start = 0.;
|
||||
vec4 starts = vec4(0.);
|
||||
vec4 ends = vec4(0.);
|
||||
float diff = 2.;
|
||||
vec4 m = vec4(0.);
|
||||
vec2 position = vec2(0.);
|
||||
vec4 vI4 = vec4(vI);
|
||||
int start = 0;
|
||||
ivec4 starts = ivec4(0);
|
||||
ivec4 ends = ivec4(0);
|
||||
int diff = 2;
|
||||
ivec4 m = ivec4(0);
|
||||
ivec2 position = ivec2(0);
|
||||
ivec4 vI4 = ivec4(vI);
|
||||
|
||||
ivec2 relativePosition = ivec2(0);
|
||||
int end = 0;
|
||||
ivec2 pos1 = ivec2(0);
|
||||
ivec2 pos2 = ivec2(0);
|
||||
ivec2 pos3 = ivec2(0);
|
||||
ivec2 pos4 = ivec2(0);
|
||||
ivec3 vI3 = ivec3(vI);
|
||||
ivec3 mask = ivec3(0);
|
||||
|
||||
// traverse the different levels of the pyramid
|
||||
for(int i = 1; i < 14; i++) {
|
||||
if(float(i) >= uLevels) break;
|
||||
|
||||
offset -= diff;
|
||||
diff *= 2.;
|
||||
relativePosition = position + k * vec2(offset, 0.);
|
||||
offset.x -= diff;
|
||||
diff *= 2;
|
||||
relativePosition = position + offset;
|
||||
|
||||
ends = partialSums.wzyx + vec4(start);
|
||||
starts = vec4(start, ends.xyz);
|
||||
m = vec4(greaterThanEqual(vI4, starts)) * vec4(lessThan(vI4, ends));
|
||||
relativePosition += m.y * vec2(k, 0.) + m.z * vec2(0., k) + m.w * vec2(k, k);
|
||||
end = start + pyramidVoxel(vec2(relativePosition));
|
||||
pos1 = ivec2(relativePosition);
|
||||
starts.x = start;
|
||||
ends.x = end;
|
||||
pos2 = ivec2(relativePosition + ivec2(1, 0));
|
||||
starts.y = ends.x;
|
||||
ends.y = ends.x + pyramidVoxel(vec2(pos2));
|
||||
pos3 = relativePosition + ivec2(0, 1);
|
||||
starts.z = ends.y;
|
||||
ends.z = ends.y + pyramidVoxel(vec2(pos3));
|
||||
pos4 = relativePosition + ivec2(1, 1);
|
||||
starts.w = ends.z;
|
||||
mask = ivec3(greaterThanEqual(vI3, starts.rgb)) * ivec3(lessThan(vI3, ends.rgb));
|
||||
m = ivec4(mask, 1 - int(any(bvec3(mask))));
|
||||
|
||||
start = dot(m, starts);
|
||||
position = 2. * (relativePosition - k * vec2(offset, 0.));
|
||||
partialSums = texture2D(tActiveVoxelsPyramid, relativePosition);
|
||||
relativePosition = m.x * pos1 + m.y * pos2 + m.z * pos3 + m.w * pos4;
|
||||
start = idot4(m, starts);
|
||||
position = 2 * (relativePosition - offset);
|
||||
}
|
||||
|
||||
ends = partialSums.wzyx + vec4(start);
|
||||
starts = vec4(start, ends.xyz);
|
||||
m = vec4(greaterThanEqual(vI4, starts)) * vec4(lessThan(vI4, ends));
|
||||
position += m.y * vec2(k, 0.) + m.z * vec2(0., k) + m.w * vec2(k, k);
|
||||
end = start + int(baseVoxel(vec2(position)).r * 255.0);
|
||||
pos1 = position;
|
||||
starts.x = start;
|
||||
ends.x = end;
|
||||
pos2 = position + ivec2(1, 0);
|
||||
starts.y = ends.x;
|
||||
ends.y = ends.x + int(baseVoxel(vec2(pos2)).r * 255.0);
|
||||
pos3 = position + ivec2(0, 1);
|
||||
starts.z = ends.y;
|
||||
ends.z = ends.y + int(baseVoxel(vec2(pos3)).r * 255.0);
|
||||
pos4 = position + ivec2(1, 1);
|
||||
starts.w = ends.z;
|
||||
mask = ivec3(greaterThanEqual(vI3, starts.rgb)) * ivec3(lessThan(vI3, ends.rgb));
|
||||
m = ivec4(mask, 1 - int(any(bvec3(mask))));
|
||||
position = m.x * pos1 + m.y * pos2 + m.z * pos3 + m.w * pos4;
|
||||
|
||||
vec2 coord2d = position / uScale;
|
||||
vec2 coord2d = (vec2(position) / uSize) / uScale;
|
||||
vec3 coord3d = floor(index3dFrom2d(coord2d) + 0.5);
|
||||
|
||||
float edgeIndex = floor(texture2D(tActiveVoxelsBase, position).a * 255.0 + 0.5);
|
||||
float edgeIndex = floor(baseVoxel(vec2(position)).a * 255.0 + 0.5);
|
||||
|
||||
// current vertex for the up to 15 MC cases
|
||||
float currentVertex = vI - dot(m, starts);
|
||||
int currentVertex = vI - idot4(m, starts);
|
||||
|
||||
// get index into triIndices table
|
||||
float mcIndex = 16. * edgeIndex + currentVertex;
|
||||
vec4 mcData = texture2D(tTriIndices, vec2(intMod(mcIndex, 64.), floor(mcIndex / 64.)) / 64.);
|
||||
int mcIndex = 16 * int(edgeIndex) + currentVertex;
|
||||
vec4 mcData = texture2D(tTriIndices, vec2(imod(mcIndex, 64), mcIndex / 64) / 64.);
|
||||
|
||||
// bit mask to avoid conditionals (see comment below) for getting MC case corner
|
||||
vec4 m0 = vec4(floor(mcData.a * 255.0 + 0.5));
|
||||
@@ -188,30 +243,41 @@ void main(void) {
|
||||
// group id
|
||||
#if __VERSION__ == 100
|
||||
// webgl1 does not support 'flat' interpolation (i.e. no interpolation)
|
||||
// so we ensure a constant group id per triangle
|
||||
gl_FragData[0].w = decodeFloatRGB(voxel(coord3d).rgb);
|
||||
// so we ensure a constant group id per triangle here
|
||||
#ifdef dPackedGroup
|
||||
gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0);
|
||||
#else
|
||||
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
|
||||
float group = coord3d.z + coord3d.y * gridDim.z + coord3d.x * gridDim.z * gridDim.y;
|
||||
gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
#else
|
||||
gl_FragData[0].w = t < 0.5 ? decodeFloatRGB(d0.rgb) : decodeFloatRGB(d1.rgb);
|
||||
#ifdef dPackedGroup
|
||||
gl_FragData[1] = vec4(t < 0.5 ? d0.rgb : d1.rgb, 1.0);
|
||||
#else
|
||||
vec3 b = t < 0.5 ? b0 : b1;
|
||||
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
|
||||
float group = b.z + b.y * gridDim.z + b.x * gridDim.z * gridDim.y;
|
||||
gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// normals from gradients
|
||||
vec3 n0 = -normalize(vec3(
|
||||
voxel2(b0 - c1).a - voxel2(b0 + c1).a,
|
||||
voxel2(b0 - c3).a - voxel2(b0 + c3).a,
|
||||
voxel2(b0 - c4).a - voxel2(b0 + c4).a
|
||||
voxelPadded(b0 - c1).a - voxelPadded(b0 + c1).a,
|
||||
voxelPadded(b0 - c3).a - voxelPadded(b0 + c3).a,
|
||||
voxelPadded(b0 - c4).a - voxelPadded(b0 + c4).a
|
||||
));
|
||||
vec3 n1 = -normalize(vec3(
|
||||
voxel2(b1 - c1).a - voxel2(b1 + c1).a,
|
||||
voxel2(b1 - c3).a - voxel2(b1 + c3).a,
|
||||
voxel2(b1 - c4).a - voxel2(b1 + c4).a
|
||||
voxelPadded(b1 - c1).a - voxelPadded(b1 + c1).a,
|
||||
voxelPadded(b1 - c3).a - voxelPadded(b1 + c3).a,
|
||||
voxelPadded(b1 - c4).a - voxelPadded(b1 + c4).a
|
||||
));
|
||||
gl_FragData[1].xyz = -vec3(
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform)));
|
||||
gl_FragData[2].xyz = normalMatrix * -vec3(
|
||||
n0.x + t * (n0.x - n1.x),
|
||||
n0.y + t * (n0.y - n1.y),
|
||||
n0.z + t * (n0.z - n1.z)
|
||||
);
|
||||
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform)));
|
||||
gl_FragData[1].xyz = normalMatrix * gl_FragData[1].xyz;
|
||||
}
|
||||
`;
|
||||
@@ -7,6 +7,7 @@
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
#include common
|
||||
#include read_from_texture
|
||||
@@ -16,19 +17,17 @@ precision highp int;
|
||||
|
||||
#ifdef dGeoTexture
|
||||
uniform vec2 uGeoTexDim;
|
||||
uniform sampler2D tPositionGroup;
|
||||
uniform sampler2D tPosition;
|
||||
uniform sampler2D tGroup;
|
||||
uniform sampler2D tNormal;
|
||||
#else
|
||||
attribute vec3 aPosition;
|
||||
attribute float aGroup;
|
||||
attribute vec3 aNormal;
|
||||
#endif
|
||||
attribute mat4 aTransform;
|
||||
attribute float aInstance;
|
||||
attribute float aGroup;
|
||||
|
||||
#ifdef dGeoTexture
|
||||
uniform sampler2D tNormal;
|
||||
#else
|
||||
attribute vec3 aNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
|
||||
void main(){
|
||||
@@ -40,7 +39,7 @@ void main(){
|
||||
#include clip_instance
|
||||
|
||||
#ifdef dGeoTexture
|
||||
vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
|
||||
vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 normal = aNormal;
|
||||
#endif
|
||||
|
||||
@@ -127,12 +127,14 @@ function waitForGpuCommandsCompleteSync(gl: GLRenderingContext): void {
|
||||
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel);
|
||||
}
|
||||
|
||||
export function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) {
|
||||
export function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array | Int32Array) {
|
||||
if (isDebugMode) checkFramebufferStatus(gl);
|
||||
if (buffer instanceof Uint8Array) {
|
||||
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
|
||||
} else if (buffer instanceof Float32Array) {
|
||||
gl.readPixels(x, y, width, height, gl.RGBA, gl.FLOAT, buffer);
|
||||
} else if (buffer instanceof Int32Array && isWebGL2(gl)) {
|
||||
gl.readPixels(x, y, width, height, gl.RGBA_INTEGER, gl.INT, buffer);
|
||||
} else {
|
||||
throw new Error('unsupported readPixels buffer type');
|
||||
}
|
||||
@@ -186,6 +188,7 @@ export interface WebGLContext {
|
||||
readonly resources: WebGLResources
|
||||
|
||||
readonly maxTextureSize: number
|
||||
readonly max3dTextureSize: number
|
||||
readonly maxRenderbufferSize: number
|
||||
readonly maxDrawBuffers: number
|
||||
readonly maxTextureImageUnits: number
|
||||
@@ -204,7 +207,7 @@ export interface WebGLContext {
|
||||
|
||||
createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32' | 'fp16', filter?: TextureFilter) => RenderTarget
|
||||
unbindFramebuffer: () => void
|
||||
readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => void
|
||||
readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array | Int32Array) => void
|
||||
readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
|
||||
waitForGpuCommandsComplete: () => Promise<void>
|
||||
waitForGpuCommandsCompleteSync: () => void
|
||||
@@ -221,8 +224,9 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
|
||||
const parameters = {
|
||||
maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
|
||||
max3dTextureSize: isWebGL2(gl) ? gl.getParameter(gl.MAX_3D_TEXTURE_SIZE) as number : 0,
|
||||
maxRenderbufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
|
||||
maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) as number : 0,
|
||||
maxDrawBuffers: extensions.drawBuffers ? gl.getParameter(extensions.drawBuffers.MAX_DRAW_BUFFERS) as number : 0,
|
||||
maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number,
|
||||
maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) as number,
|
||||
};
|
||||
@@ -287,6 +291,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
resources,
|
||||
|
||||
get maxTextureSize () { return parameters.maxTextureSize; },
|
||||
get max3dTextureSize () { return parameters.max3dTextureSize; },
|
||||
get maxRenderbufferSize () { return parameters.maxRenderbufferSize; },
|
||||
get maxDrawBuffers () { return parameters.maxDrawBuffers; },
|
||||
get maxTextureImageUnits () { return parameters.maxTextureImageUnits; },
|
||||
@@ -330,7 +335,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
};
|
||||
},
|
||||
unbindFramebuffer: () => unbindFramebuffer(gl),
|
||||
readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => {
|
||||
readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array | Int32Array) => {
|
||||
readPixels(gl, x, y, width, height, buffer);
|
||||
},
|
||||
readPixelsAsync,
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -112,7 +112,12 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
const { instancedArrays, vertexArrayObject } = ctx.extensions;
|
||||
|
||||
// emulate gl_VertexID when needed
|
||||
if (!ctx.isWebGL2 && values.uVertexCount) {
|
||||
// if (!ctx.isWebGL2 && values.uVertexCount) {
|
||||
// not using gl_VertexID in WebGL2 but aVertex to ensure there is an active attribute with divisor 0
|
||||
// since FF 85 this is not needed anymore but lets keep it for backwards compatibility
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1679693
|
||||
// see also note in src/mol-gl/shader/chunks/common-vert-params.glsl.ts
|
||||
if (values.uVertexCount) {
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
(values as any).aVertex = ValueCell.create(fillSerial(new Float32Array(vertexCount)));
|
||||
(schema as any).aVertex = AttributeSpec('float32', 1, 0);
|
||||
@@ -338,6 +343,11 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
});
|
||||
attributeBuffers.forEach(([_, buffer]) => buffer.destroy());
|
||||
if (elementsBuffer) elementsBuffer.destroy();
|
||||
|
||||
stats.drawCount -= drawCount;
|
||||
stats.instanceCount -= instanceCount;
|
||||
stats.instancedDrawCount -= instanceCount * drawCount;
|
||||
|
||||
destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export type TextureKindValue = {
|
||||
'image-uint8': TextureImage<Uint8Array>
|
||||
'image-float32': TextureImage<Float32Array>
|
||||
'image-float16': TextureImage<Float32Array>
|
||||
'image-int32': TextureImage<Int32Array>
|
||||
'image-depth': TextureImage<Uint8Array> // TODO should be Uint32Array
|
||||
'volume-uint8': TextureVolume<Uint8Array>
|
||||
'volume-float32': TextureVolume<Float32Array>
|
||||
@@ -28,7 +29,7 @@ export type TextureKindValue = {
|
||||
}
|
||||
export type TextureValueType = ValueOf<TextureKindValue>
|
||||
export type TextureKind = keyof TextureKindValue
|
||||
export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16'
|
||||
export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int'
|
||||
export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
|
||||
/** Numbers are shortcuts for color attachment */
|
||||
export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
@@ -43,6 +44,7 @@ export function getTarget(gl: GLRenderingContext, kind: TextureKind): number {
|
||||
}
|
||||
if (isWebGL2(gl)) {
|
||||
switch (kind) {
|
||||
case 'image-int32': return gl.TEXTURE_2D;
|
||||
case 'volume-uint8': return gl.TEXTURE_3D;
|
||||
case 'volume-float32': return gl.TEXTURE_3D;
|
||||
case 'volume-float16': return gl.TEXTURE_3D;
|
||||
@@ -55,9 +57,14 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T
|
||||
switch (format) {
|
||||
case 'alpha':
|
||||
if (isWebGL2(gl) && type === 'float') return gl.RED;
|
||||
else if (isWebGL2(gl) && type === 'int') return gl.RED_INTEGER;
|
||||
else return gl.ALPHA;
|
||||
case 'rgb': return gl.RGB;
|
||||
case 'rgba': return gl.RGBA;
|
||||
case 'rgb':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
|
||||
return gl.RGB;
|
||||
case 'rgba':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER;
|
||||
return gl.RGBA;
|
||||
case 'depth': return gl.DEPTH_COMPONENT;
|
||||
}
|
||||
}
|
||||
@@ -70,18 +77,21 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
|
||||
case 'ubyte': return gl.ALPHA;
|
||||
case 'float': return gl.R32F;
|
||||
case 'fp16': return gl.R16F;
|
||||
case 'int': return gl.R32I;
|
||||
}
|
||||
case 'rgb':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RGB;
|
||||
case 'float': return gl.RGB32F;
|
||||
case 'fp16': return gl.RGB16F;
|
||||
case 'int': return gl.RGB32I;
|
||||
}
|
||||
case 'rgba':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RGBA;
|
||||
case 'float': return gl.RGBA32F;
|
||||
case 'fp16': return gl.RGBA16F;
|
||||
case 'int': return gl.RGBA32I;
|
||||
}
|
||||
case 'depth':
|
||||
return gl.DEPTH_COMPONENT16;
|
||||
@@ -110,6 +120,7 @@ function getTypeSize(type: TextureType): number {
|
||||
case 'ushort': return 2;
|
||||
case 'float': return 4;
|
||||
case 'fp16': return 2;
|
||||
case 'int': return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +132,9 @@ export function getType(gl: GLRenderingContext, extensions: WebGLExtensions, typ
|
||||
case 'fp16':
|
||||
if (extensions.textureHalfFloat) return extensions.textureHalfFloat.HALF_FLOAT;
|
||||
else throw new Error('extension "texture_half_float" unavailable');
|
||||
case 'int':
|
||||
if (isWebGL2(gl)) return gl.INT;
|
||||
else throw new Error('texture type "int" requires webgl2');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +228,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
(kind.endsWith('float32') && _type !== 'float') ||
|
||||
(kind.endsWith('float16') && _type !== 'fp16') ||
|
||||
(kind.endsWith('uint8') && _type !== 'ubyte') ||
|
||||
(kind.endsWith('int32') && _type !== 'int') ||
|
||||
(kind.endsWith('depth') && _type !== 'ushort')
|
||||
) {
|
||||
throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`);
|
||||
@@ -295,7 +310,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
if (layer === undefined) throw new Error('need `layer` to attach 3D texture');
|
||||
gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), texture, 0, layer);
|
||||
} else {
|
||||
throw new Error('unknown texture target');
|
||||
throw new Error('unknown/unsupported texture target');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ type _GaussianDensityTextureData = {
|
||||
|
||||
function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
|
||||
// console.log('2d');
|
||||
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = webgl;
|
||||
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat, blendMinMax } } = webgl;
|
||||
const { smoothness, resolution } = props;
|
||||
|
||||
const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
|
||||
@@ -190,11 +190,13 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
setupDensityRendering(webgl, renderable);
|
||||
render(texture, true);
|
||||
|
||||
setupMinDistanceRendering(webgl, renderable);
|
||||
render(minDistTex, true);
|
||||
if (blendMinMax) {
|
||||
setupMinDistanceRendering(webgl, renderable);
|
||||
render(minDistTex, true);
|
||||
|
||||
setupGroupIdRendering(webgl, renderable);
|
||||
render(texture, false);
|
||||
setupGroupIdRendering(webgl, renderable);
|
||||
render(texture, false);
|
||||
}
|
||||
|
||||
// printTexture(webgl, minDistTex, 0.75);
|
||||
|
||||
|
||||
@@ -111,33 +111,41 @@ namespace Spacegroup {
|
||||
|
||||
const _translationRef = Vec3();
|
||||
const _translationRefSymop = Vec3();
|
||||
const _translationRefOffset = Vec3();
|
||||
const _translationSymop = Vec3();
|
||||
export function setOperatorMatrixRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3, target: Mat4) {
|
||||
Vec3.set(_ijkVec, i, j, k);
|
||||
Vec3.floor(_translationRef, ref);
|
||||
|
||||
Mat4.copy(target, spacegroup.operators[index]);
|
||||
|
||||
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, target));
|
||||
|
||||
Mat4.getTranslation(_translationSymop, target);
|
||||
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
|
||||
Vec3.add(_translationSymop, _translationSymop, _translationRef);
|
||||
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
|
||||
|
||||
Mat4.setTranslation(target, _translationSymop);
|
||||
Mat4.mul(target, spacegroup.cell.fromFractional, target);
|
||||
Mat4.mul(target, target, spacegroup.cell.toFractional);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Symmetry operator for transformation around the given
|
||||
* reference point `ref` in fractional coordinates
|
||||
*/
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
|
||||
const operator = Mat4.zero();
|
||||
|
||||
Vec3.set(_ijkVec, i, j, k);
|
||||
Vec3.floor(_translationRef, ref);
|
||||
|
||||
Mat4.copy(operator, spacegroup.operators[spgrOp]);
|
||||
|
||||
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, operator));
|
||||
|
||||
Mat4.getTranslation(_translationSymop, operator);
|
||||
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
|
||||
Vec3.add(_translationSymop, _translationSymop, _translationRef);
|
||||
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
|
||||
|
||||
Mat4.setTranslation(operator, _translationSymop);
|
||||
Mat4.mul(operator, spacegroup.cell.fromFractional, operator);
|
||||
Mat4.mul(operator, operator, spacegroup.cell.toFractional);
|
||||
|
||||
Vec3.sub(_translationRefOffset, _translationRefSymop, _translationRef);
|
||||
|
||||
const _i = i - _translationRefOffset[0];
|
||||
const _j = j - _translationRefOffset[1];
|
||||
const _k = k - _translationRefOffset[2];
|
||||
|
||||
// const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + _i}${5 + _j}${5 + _k}`, operator, { hkl: Vec3.create(_i, _j, _k), spgrOp });
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -74,11 +74,11 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
|
||||
let cI = 0;
|
||||
let seqId = 0;
|
||||
for (let i = 0, il = seqIds.length; i < il; ++i) {
|
||||
if (chainOffsets[cI] > residueOffsets[i]) {
|
||||
if (residueOffsets[i] > chainOffsets[cI + 1]) {
|
||||
cI += 1;
|
||||
seqId = 0;
|
||||
}
|
||||
seqIds[i] = ++seqId;
|
||||
seqIds[i] = ++seqId; // start id on one
|
||||
}
|
||||
residues.label_seq_id = Column.ofIntArray(seqIds);
|
||||
}
|
||||
|
||||
@@ -251,8 +251,8 @@ type CifCoreFormat = ModelFormat<CifCoreFormat.Data>
|
||||
|
||||
namespace CifCoreFormat {
|
||||
export type Data = { db: CifCore_Database, frame: CifFrame }
|
||||
export function is(x: ModelFormat): x is CifCoreFormat {
|
||||
return x.kind === 'cifCore';
|
||||
export function is(x?: ModelFormat): x is CifCoreFormat {
|
||||
return x?.kind === 'cifCore';
|
||||
}
|
||||
|
||||
export function fromFrame(frame: CifFrame, db?: CifCore_Database): CifCoreFormat {
|
||||
|
||||
@@ -50,6 +50,7 @@ const StandardComponents = (function() {
|
||||
{ id: 'ASP', name: 'ASPARTIC ACID', type: 'L-peptide linking' },
|
||||
{ id: 'GLU', name: 'GLUTAMIC ACID', type: 'L-peptide linking' },
|
||||
{ id: 'THR', name: 'THREONINE', type: 'L-peptide linking' },
|
||||
{ id: 'PRO', name: 'PROLINE', type: 'L-peptide linking' },
|
||||
{ id: 'SEC', name: 'SELENOCYSTEINE', type: 'L-peptide linking' },
|
||||
{ id: 'PYL', name: 'PYRROLYSINE', type: 'L-peptide linking' },
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ export { CubeFormat };
|
||||
type CubeFormat = ModelFormat<CubeFile>
|
||||
|
||||
namespace MolFormat {
|
||||
export function is(x: ModelFormat): x is CubeFormat {
|
||||
return x.kind === 'cube';
|
||||
export function is(x?: ModelFormat): x is CubeFormat {
|
||||
return x?.kind === 'cube';
|
||||
}
|
||||
|
||||
export function create(cube: CubeFile): CubeFormat {
|
||||
|
||||
@@ -106,8 +106,8 @@ export { GroFormat };
|
||||
type GroFormat = ModelFormat<GroFile>
|
||||
|
||||
namespace GroFormat {
|
||||
export function is(x: ModelFormat): x is GroFormat {
|
||||
return x.kind === 'gro';
|
||||
export function is(x?: ModelFormat): x is GroFormat {
|
||||
return x?.kind === 'gro';
|
||||
}
|
||||
|
||||
export function fromGro(gro: GroFile): GroFormat {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -79,14 +79,22 @@ export { MmcifFormat };
|
||||
type MmcifFormat = ModelFormat<MmcifFormat.Data>
|
||||
|
||||
namespace MmcifFormat {
|
||||
export type Data = { db: mmCIF_Database, frame: CifFrame }
|
||||
export function is(x: ModelFormat): x is MmcifFormat {
|
||||
return x.kind === 'mmCIF';
|
||||
export type Data = {
|
||||
db: mmCIF_Database,
|
||||
frame: CifFrame,
|
||||
/**
|
||||
* Original source format. Some formats, including PDB, are converted
|
||||
* to mmCIF before further processing.
|
||||
*/
|
||||
source?: ModelFormat
|
||||
}
|
||||
export function is(x?: ModelFormat): x is MmcifFormat {
|
||||
return x?.kind === 'mmCIF';
|
||||
}
|
||||
|
||||
export function fromFrame(frame: CifFrame, db?: mmCIF_Database): MmcifFormat {
|
||||
export function fromFrame(frame: CifFrame, db?: mmCIF_Database, source?: ModelFormat): MmcifFormat {
|
||||
if (!db) db = CIF.schema.mmCIF(frame);
|
||||
return { kind: 'mmCIF', name: db._name, data: { db, frame } };
|
||||
return { kind: 'mmCIF', name: db._name, data: { db, frame, source } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ export { MolFormat };
|
||||
type MolFormat = ModelFormat<MolFile>
|
||||
|
||||
namespace MolFormat {
|
||||
export function is(x: ModelFormat): x is MolFormat {
|
||||
return x.kind === 'mol';
|
||||
export function is(x?: ModelFormat): x is MolFormat {
|
||||
return x?.kind === 'mol';
|
||||
}
|
||||
|
||||
export function create(mol: MolFile): MolFormat {
|
||||
|
||||
@@ -99,8 +99,8 @@ export { Mol2Format };
|
||||
type Mol2Format = ModelFormat<Mol2File>
|
||||
|
||||
namespace Mol2Format {
|
||||
export function is(x: ModelFormat): x is Mol2Format {
|
||||
return x.kind === 'mol2';
|
||||
export function is(x?: ModelFormat): x is Mol2Format {
|
||||
return x?.kind === 'mol2';
|
||||
}
|
||||
|
||||
export function create(mol2: Mol2File): Mol2Format {
|
||||
|
||||
@@ -13,12 +13,27 @@ import { createModels } from './basic/parser';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { AtomPartialCharge } from './property/partial-charge';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { ModelFormat } from '../format';
|
||||
|
||||
export { PdbFormat };
|
||||
|
||||
type PdbFormat = ModelFormat<PdbFile>
|
||||
|
||||
namespace PdbFormat {
|
||||
export function is(x?: ModelFormat): x is PdbFormat {
|
||||
return x?.kind === 'pdb';
|
||||
}
|
||||
|
||||
export function create(pdb: PdbFile): PdbFormat {
|
||||
return { kind: 'pdb', name: pdb.id || '', data: pdb };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromPDB(pdb: PdbFile): Task<Trajectory> {
|
||||
return Task.create('Parse PDB', async ctx => {
|
||||
await ctx.update('Converting to mmCIF');
|
||||
const cif = await pdbToMmCif(pdb);
|
||||
const format = MmcifFormat.fromFrame(cif);
|
||||
const format = MmcifFormat.fromFrame(cif, undefined, PdbFormat.create(pdb));
|
||||
const models = await createModels(format.data.db, format, ctx);
|
||||
const partial_charge = cif.categories['atom_site']?.getField('partial_charge');
|
||||
if (partial_charge) {
|
||||
|
||||
@@ -17,7 +17,6 @@ export function getAtomSiteTemplate(data: string, count: number) {
|
||||
const ts = () => TokenBuilder.create(data, 2 * count);
|
||||
return {
|
||||
index: 0,
|
||||
count,
|
||||
group_PDB: ts(),
|
||||
id: str(),
|
||||
auth_atom_id: ts(),
|
||||
@@ -61,7 +60,7 @@ export function getAtomSite(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema
|
||||
label_asym_id: auth_asym_id,
|
||||
label_atom_id: auth_atom_id,
|
||||
label_comp_id: auth_comp_id,
|
||||
label_seq_id: CifField.ofUndefined(sites.count, Column.Schema.int),
|
||||
label_seq_id: CifField.ofUndefined(sites.index, Column.Schema.int),
|
||||
label_entity_id: CifField.ofStrings(sites.label_entity_id),
|
||||
|
||||
occupancy: CifField.ofTokens(sites.occupancy),
|
||||
|
||||
@@ -103,8 +103,8 @@ export { PsfFormat };
|
||||
type PsfFormat = ModelFormat<PsfFile>
|
||||
|
||||
namespace PsfFormat {
|
||||
export function is(x: ModelFormat): x is PsfFormat {
|
||||
return x.kind === 'psf';
|
||||
export function is(x?: ModelFormat): x is PsfFormat {
|
||||
return x?.kind === 'psf';
|
||||
}
|
||||
|
||||
export function fromPsf(psf: PsfFile): PsfFormat {
|
||||
|
||||
@@ -98,8 +98,8 @@ export { Ccp4Format };
|
||||
type Ccp4Format = ModelFormat<Ccp4File>
|
||||
|
||||
namespace Ccp4Format {
|
||||
export function is(x: ModelFormat): x is Ccp4Format {
|
||||
return x.kind === 'ccp4';
|
||||
export function is(x?: ModelFormat): x is Ccp4Format {
|
||||
return x?.kind === 'ccp4';
|
||||
}
|
||||
|
||||
export function create(ccp4: Ccp4File): Ccp4Format {
|
||||
|
||||
@@ -71,8 +71,8 @@ export { CubeFormat };
|
||||
type CubeFormat = ModelFormat<CubeFile>
|
||||
|
||||
namespace CubeFormat {
|
||||
export function is(x: ModelFormat): x is CubeFormat {
|
||||
return x.kind === 'cube';
|
||||
export function is(x?: ModelFormat): x is CubeFormat {
|
||||
return x?.kind === 'cube';
|
||||
}
|
||||
|
||||
export function create(cube: CubeFile): CubeFormat {
|
||||
|
||||
@@ -62,8 +62,8 @@ export { DscifFormat };
|
||||
type DscifFormat = ModelFormat<DensityServer_Data_Database>
|
||||
|
||||
namespace DscifFormat {
|
||||
export function is(x: ModelFormat): x is DscifFormat {
|
||||
return x.kind === 'dscif';
|
||||
export function is(x?: ModelFormat): x is DscifFormat {
|
||||
return x?.kind === 'dscif';
|
||||
}
|
||||
|
||||
export function create(dscif: DensityServer_Data_Database): DscifFormat {
|
||||
|
||||
@@ -60,8 +60,8 @@ export { Dsn6Format };
|
||||
type Dsn6Format = ModelFormat<Dsn6File>
|
||||
|
||||
namespace Dsn6Format {
|
||||
export function is(x: ModelFormat): x is Dsn6Format {
|
||||
return x.kind === 'dsn6';
|
||||
export function is(x?: ModelFormat): x is Dsn6Format {
|
||||
return x?.kind === 'dsn6';
|
||||
}
|
||||
|
||||
export function create(dsn6: Dsn6File): Dsn6Format {
|
||||
|
||||
@@ -48,8 +48,8 @@ export { DxFormat };
|
||||
type DxFormat = ModelFormat<DxFile>
|
||||
|
||||
namespace DxFormat {
|
||||
export function is(x: ModelFormat): x is DxFormat {
|
||||
return x.kind === 'dx';
|
||||
export function is(x?: ModelFormat): x is DxFormat {
|
||||
return x?.kind === 'dx';
|
||||
}
|
||||
|
||||
export function create(dx: DxFile): DxFormat {
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace CustomElementProperty {
|
||||
type: builder.type || 'dynamic',
|
||||
defaultParams: {},
|
||||
getParams: (data: Model) => ({}),
|
||||
isApplicable: (data: Model) => !!builder.isApplicable?.(data),
|
||||
isApplicable: (data: Model) => !builder.isApplicable || !!builder.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model) => {
|
||||
return await builder.getData(data, ctx);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
|
||||
import { computeCarbohydrates } from './carbohydrates/compute';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { idFactory } from '../../../mol-util/id-factory';
|
||||
import { GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
@@ -50,6 +50,7 @@ class Structure {
|
||||
interUnitBonds?: InterUnitBonds,
|
||||
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
|
||||
unitSymmetryGroupsIndexMap?: IntMap<number>,
|
||||
unitsSortedByVolume?: ReadonlyArray<Unit>;
|
||||
carbohydrates?: Carbohydrates,
|
||||
models?: ReadonlyArray<Model>,
|
||||
model?: Model,
|
||||
@@ -257,6 +258,13 @@ class Structure {
|
||||
return this._props.unitSymmetryGroupsIndexMap;
|
||||
}
|
||||
|
||||
/** Array of all units in the structure, sorted by their boundary volume */
|
||||
get unitsSortedByVolume(): ReadonlyArray<Unit> {
|
||||
if (this._props.unitsSortedByVolume) return this._props.unitsSortedByVolume;
|
||||
this._props.unitsSortedByVolume = getUnitsSortedByVolume(this);
|
||||
return this._props.unitsSortedByVolume;
|
||||
}
|
||||
|
||||
get carbohydrates(): Carbohydrates {
|
||||
if (this._props.carbohydrates) return this._props.carbohydrates;
|
||||
this._props.carbohydrates = computeCarbohydrates(this);
|
||||
@@ -419,7 +427,28 @@ class Structure {
|
||||
}
|
||||
}
|
||||
|
||||
function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
|
||||
function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
|
||||
return units[i].id - units[j].id;
|
||||
|
||||
}
|
||||
function cmpUnitGroupVolume(units: ArrayLike<[index: number, volume: number]>, i: number, j: number) {
|
||||
const d = units[i][1] - units[j][1];
|
||||
if (d === 0) return units[i][0] - units[j][0];
|
||||
return d;
|
||||
}
|
||||
|
||||
function getUnitsSortedByVolume(structure: Structure) {
|
||||
const { unitSymmetryGroups } = structure;
|
||||
const groups = unitSymmetryGroups.map((g, i) => [i, Box3D.volume(g.units[0].lookup3d.boundary.box)] as [number, number]);
|
||||
sort(groups, 0, groups.length, cmpUnitGroupVolume, arraySwap);
|
||||
const ret: Unit[] = [];
|
||||
for (const [i] of groups) {
|
||||
for (const u of unitSymmetryGroups[i].units) {
|
||||
ret.push(u);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getModels(s: Structure) {
|
||||
const { units } = s;
|
||||
@@ -1136,7 +1165,7 @@ namespace Structure {
|
||||
/** Must be lower to be medium */
|
||||
mediumResidueCount: 5000,
|
||||
/** Must be lower to be large (big ribosomes like 4UG0 should still be `large`) */
|
||||
largeResidueCount: 20000,
|
||||
largeResidueCount: 30000,
|
||||
/**
|
||||
* Structures above `largeResidueCount` are consider huge when they have
|
||||
* a `highSymmetryUnitCount` or gigantic when not
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -42,9 +42,9 @@ namespace Unit {
|
||||
|
||||
export function create<K extends Kind>(id: number, invariantId: number, chainGroupId: number, traits: Traits, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, props?: K extends Kind.Atomic ? AtomicProperties : CoarseProperties): Unit {
|
||||
switch (kind) {
|
||||
case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, traits, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, void 0), AtomicProperties(props));
|
||||
case Kind.Spheres: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), CoarseProperties(props));
|
||||
case Kind.Gaussians: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), CoarseProperties(props));
|
||||
case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, traits, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, void 0), props ?? AtomicProperties());
|
||||
case Kind.Spheres: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), props ?? CoarseProperties());
|
||||
case Kind.Gaussians: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), props ?? CoarseProperties());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,8 +165,8 @@ namespace Unit {
|
||||
}
|
||||
|
||||
|
||||
function BaseProperties(props?: BaseProperties): BaseProperties {
|
||||
return { ...props };
|
||||
function BaseProperties(): BaseProperties {
|
||||
return {};
|
||||
}
|
||||
|
||||
function getSphereRadiusFunc(model: Model) {
|
||||
@@ -347,8 +347,8 @@ namespace Unit {
|
||||
residueCount?: number
|
||||
}
|
||||
|
||||
function AtomicProperties(props?: AtomicProperties): AtomicProperties {
|
||||
return { ...BaseProperties(props), ...props };
|
||||
function AtomicProperties(): AtomicProperties {
|
||||
return BaseProperties();
|
||||
}
|
||||
|
||||
class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base {
|
||||
@@ -383,8 +383,12 @@ namespace Unit {
|
||||
const modelCoarseConformation = getCoarseConformation(this.kind, model);
|
||||
|
||||
if (!props) {
|
||||
const boundary = Unit.isSameConformation(this as Unit.Spheres | Unit.Gaussians, model) ? this.props.boundary : undefined; // TODO get rid of casting
|
||||
props = { ...this.props, boundary, lookup3d: undefined, principalAxes: undefined };
|
||||
props = { ...this.props };
|
||||
if (!Unit.isSameConformation(this as Unit.Spheres | Unit.Gaussians, model)) { // TODO get rid of casting
|
||||
props.boundary = undefined;
|
||||
props.lookup3d = undefined;
|
||||
props.principalAxes = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const conformation = coarseConformation !== modelCoarseConformation
|
||||
@@ -453,17 +457,17 @@ namespace Unit {
|
||||
|
||||
interface CoarseProperties extends BaseProperties { }
|
||||
|
||||
function CoarseProperties(props?: CoarseProperties): CoarseProperties {
|
||||
return BaseProperties(props);
|
||||
}
|
||||
|
||||
function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, chainGroupId: number, traits: Traits, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties): Unit {
|
||||
return new Coarse(id, invariantId, chainGroupId, traits, model, kind, elements, conformation, props) as any as Unit /** lets call this an ugly temporary hack */;
|
||||
function CoarseProperties(): CoarseProperties {
|
||||
return BaseProperties();
|
||||
}
|
||||
|
||||
export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { }
|
||||
export class Gaussians extends Coarse<Kind.Gaussians, CoarseGaussianConformation> { }
|
||||
|
||||
function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, chainGroupId: number, traits: Traits, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties): K extends Kind.Spheres ? Spheres : Gaussians {
|
||||
return new Coarse(id, invariantId, chainGroupId, traits, model, kind, elements, conformation, props) as any /** lets call this an ugly temporary hack */;
|
||||
}
|
||||
|
||||
export function areSameChainOperatorGroup(a: Unit, b: Unit) {
|
||||
return a.chainGroupId === b.chainGroupId && a.conformation.operator.name === b.conformation.operator.name;
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
|
||||
};
|
||||
const gaussianProps = {
|
||||
radiusOffset: structure.isCoarseGrained ? 2 : 0,
|
||||
smoothness: structure.isCoarseGrained ? 0.5 : 1.5,
|
||||
smoothness: structure.isCoarseGrained ? 1.0 : 1.5,
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
@@ -248,18 +248,18 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
Object.assign(gaussianProps, {
|
||||
traceOnly: true,
|
||||
radiusOffset: 2,
|
||||
smoothness: 0.5,
|
||||
smoothness: 1,
|
||||
visuals: ['structure-gaussian-surface-mesh']
|
||||
});
|
||||
} else if(size === Structure.Size.Huge) {
|
||||
Object.assign(gaussianProps, {
|
||||
radiusOffset: structure.isCoarseGrained ? 2 : 0,
|
||||
smoothness: 0.5,
|
||||
smoothness: 1,
|
||||
});
|
||||
} else if(structure.isCoarseGrained) {
|
||||
Object.assign(gaussianProps, {
|
||||
radiusOffset: 2,
|
||||
smoothness: 0.5,
|
||||
smoothness: 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export function registerDefault(ctx: PluginContext) {
|
||||
export function Canvas3DSetSettings(ctx: PluginContext) {
|
||||
PluginCommands.Canvas3D.ResetSettings.subscribe(ctx, () => {
|
||||
ctx.canvas3d?.setProps(DefaultCanvas3DParams);
|
||||
ctx.events.canvas3d.settingsUpdated.next();
|
||||
});
|
||||
|
||||
PluginCommands.Canvas3D.SetSettings.subscribe(ctx, e => {
|
||||
|
||||
@@ -76,11 +76,18 @@ export function UpdateRepresentationVisibility(ctx: PluginContext) {
|
||||
ctx.state.data.events.cell.stateUpdated.subscribe(e => {
|
||||
const cell = e.state.cells.get(e.ref)!;
|
||||
if (!SO.isRepresentation3D(cell.obj)) return;
|
||||
updateVisibility(cell, cell.obj.data.repr);
|
||||
ctx.canvas3d?.syncVisibility();
|
||||
|
||||
if (updateVisibility(cell, cell.obj.data.repr)) {
|
||||
ctx.canvas3d?.syncVisibility();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateVisibility(cell: StateObjectCell, r: Representation<any>) {
|
||||
r.setState({ visible: !cell.state.isHidden });
|
||||
if (r.state.visible === cell.state.isHidden) {
|
||||
r.setState({ visible: !cell.state.isHidden });
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { State, StateTransform, StateTransformer } from '../mol-state';
|
||||
import { PluginStateObject as SO } from '../mol-plugin-state/objects';
|
||||
import { Camera } from '../mol-canvas3d/camera';
|
||||
import { PluginBehavior } from './behavior';
|
||||
import { Canvas3DProps } from '../mol-canvas3d/canvas3d';
|
||||
import { Canvas3DParams, Canvas3DProps } from '../mol-canvas3d/canvas3d';
|
||||
import { PluginCommands } from './commands';
|
||||
import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
@@ -73,8 +73,9 @@ class PluginState extends PluginComponent {
|
||||
|
||||
if (snapshot.behaviour) await this.plugin.runTask(this.behaviors.setSnapshot(snapshot.behaviour));
|
||||
if (snapshot.data) await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
|
||||
if (snapshot.canvas3d) {
|
||||
if (snapshot.canvas3d.props) await PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: snapshot.canvas3d.props });
|
||||
if (snapshot.canvas3d?.props) {
|
||||
const settings = PD.normalizeParams(Canvas3DParams, snapshot.canvas3d.props, 'children');
|
||||
await PluginCommands.Canvas3D.SetSettings(this.plugin, { settings });
|
||||
}
|
||||
if (snapshot.interactivity) {
|
||||
if (snapshot.interactivity.props) this.plugin.managers.interactivity.setProps(snapshot.interactivity.props);
|
||||
|
||||
@@ -222,9 +222,11 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
// TODO
|
||||
renderObjects.length = 0;
|
||||
_renderObject = undefined;
|
||||
if (_renderObject) {
|
||||
_renderObject.state.disposed = true;
|
||||
_renderObject = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Clipping } from '../../mol-theme/clipping';
|
||||
import { Transparency } from '../../mol-theme/transparency';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
|
||||
export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, props?: PD.Values<P>, webgl?: WebGLContext) => ComplexVisual<P>): StructureRepresentation<P> {
|
||||
export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => ComplexVisual<P>): StructureRepresentation<P> {
|
||||
let version = 0;
|
||||
const { webgl } = ctx;
|
||||
const updated = new Subject<number>();
|
||||
@@ -47,11 +47,11 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
return Task.create('Creating or updating ComplexRepresentation', async runtime => {
|
||||
let newVisual = false;
|
||||
if (!visual) {
|
||||
visual = visualCtor(materialId, _props, webgl);
|
||||
visual = visualCtor(materialId, _structure, _props, webgl);
|
||||
newVisual = true;
|
||||
} else if (visual.mustRecreate?.(_props, webgl)) {
|
||||
} else if (visual.mustRecreate?.(_structure, _props, webgl)) {
|
||||
visual.destroy();
|
||||
visual = visualCtor(materialId, _props, webgl);
|
||||
visual = visualCtor(materialId, _structure, _props, webgl);
|
||||
newVisual = true;
|
||||
}
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, structure);
|
||||
|
||||
@@ -52,7 +52,7 @@ interface ComplexVisualBuilder<P extends StructureParams, G extends Geometry> {
|
||||
getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
|
||||
eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean): boolean,
|
||||
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure): void
|
||||
mustRecreate?: (props: PD.Values<P>) => boolean
|
||||
mustRecreate?: (structure: Structure, props: PD.Values<P>) => boolean
|
||||
dispose?: (geometry: G) => void
|
||||
}
|
||||
|
||||
@@ -242,7 +242,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
|
||||
},
|
||||
destroy() {
|
||||
dispose?.(geometry);
|
||||
renderObject = undefined;
|
||||
if (renderObject) {
|
||||
renderObject.state.disposed = true;
|
||||
renderObject = undefined;
|
||||
}
|
||||
},
|
||||
mustRecreate
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
|
||||
export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { }
|
||||
|
||||
export function UnitsRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, props?: PD.Values<P>, webgl?: WebGLContext) => UnitsVisual<P>): StructureRepresentation<P> {
|
||||
export function UnitsRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => UnitsVisual<P>): StructureRepresentation<P> {
|
||||
let version = 0;
|
||||
const { webgl } = ctx;
|
||||
const updated = new Subject<number>();
|
||||
@@ -59,7 +59,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
_groups = structure.unitSymmetryGroups;
|
||||
for (let i = 0; i < _groups.length; i++) {
|
||||
const group = _groups[i];
|
||||
const visual = visualCtor(materialId, _props, webgl);
|
||||
const visual = visualCtor(materialId, structure, _props, webgl);
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
setVisualState(visual, group, _state); // current state for new visual
|
||||
@@ -82,9 +82,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
// console.log('old', visualGroup.group)
|
||||
// console.log('new', group)
|
||||
let { visual } = visualGroup;
|
||||
if (visual.mustRecreate?.(_props, webgl)) {
|
||||
if (visual.mustRecreate?.({ group, structure }, _props, webgl)) {
|
||||
visual.destroy();
|
||||
visual = visualCtor(materialId, _props, webgl);
|
||||
visual = visualCtor(materialId, structure, _props, webgl);
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
setVisualState(visual, group, _state); // current state for new visual
|
||||
@@ -104,7 +104,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
} else {
|
||||
// console.log(label, 'did not find visualGroup to reuse, creating new');
|
||||
// newGroups.push(group)
|
||||
const visual = visualCtor(materialId, _props, webgl);
|
||||
const visual = visualCtor(materialId, structure, _props, webgl);
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
setVisualState(visual, group, _state); // current state for new visual
|
||||
@@ -129,9 +129,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const visualGroup = visuals.get(group.hashCode);
|
||||
if (visualGroup) {
|
||||
let { visual } = visualGroup;
|
||||
if (visual.mustRecreate?.(_props, ctx.webgl)) {
|
||||
if (visual.mustRecreate?.({ group, structure }, _props, ctx.webgl)) {
|
||||
visual.destroy();
|
||||
visual = visualCtor(materialId, _props, ctx.webgl);
|
||||
visual = visualCtor(materialId, structure, _props, ctx.webgl);
|
||||
visualGroup.visual = visual;
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
@@ -153,9 +153,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
visuals.forEach(vg => visualsList.push(vg));
|
||||
for (let i = 0, il = visualsList.length; i < il; ++i) {
|
||||
let { visual, group } = visualsList[i];
|
||||
if (visual.mustRecreate?.(_props, ctx.webgl)) {
|
||||
if (visual.mustRecreate?.({ group, structure: _structure }, _props, ctx.webgl)) {
|
||||
visual.destroy();
|
||||
visual = visualCtor(materialId, _props, webgl);
|
||||
visual = visualCtor(materialId, _structure, _props, webgl);
|
||||
visualsList[i].visual = visual;
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure: _structure });
|
||||
if (promise) await promise;
|
||||
|
||||
@@ -58,7 +58,7 @@ interface UnitsVisualBuilder<P extends StructureParams, G extends Geometry> {
|
||||
getLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number): Loci
|
||||
eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean): boolean
|
||||
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup): void
|
||||
mustRecreate?: (props: PD.Values<P>) => boolean
|
||||
mustRecreate?: (structureGroup: StructureGroup, props: PD.Values<P>) => boolean
|
||||
dispose?: (geometry: G) => void
|
||||
}
|
||||
|
||||
@@ -294,7 +294,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
|
||||
},
|
||||
destroy() {
|
||||
dispose?.(geometry);
|
||||
renderObject = undefined;
|
||||
if (renderObject) {
|
||||
renderObject.state.disposed = true;
|
||||
renderObject = undefined;
|
||||
}
|
||||
},
|
||||
mustRecreate
|
||||
};
|
||||
|
||||
@@ -157,12 +157,12 @@ export const InterUnitBondCylinderParams = {
|
||||
...BondCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
|
||||
useImpostor: PD.Boolean(true),
|
||||
tryUseImpostor: PD.Boolean(true),
|
||||
};
|
||||
export type InterUnitBondCylinderParams = typeof InterUnitBondCylinderParams
|
||||
|
||||
export function InterUnitBondCylinderVisual(materialId: number, props?: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) {
|
||||
return props?.useImpostor && webgl && webgl.extensions.fragDepth
|
||||
export function InterUnitBondCylinderVisual(materialId: number, structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) {
|
||||
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
|
||||
? InterUnitBondCylinderImpostorVisual(materialId)
|
||||
: InterUnitBondCylinderMeshVisual(materialId);
|
||||
}
|
||||
@@ -187,8 +187,8 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
|
||||
);
|
||||
},
|
||||
mustRecreate: (props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return !props.useImpostor || !webgl;
|
||||
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseImpostor || !webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -216,8 +216,8 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
|
||||
);
|
||||
},
|
||||
mustRecreate: (props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return props.useImpostor && !!webgl;
|
||||
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseImpostor && !!webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
@@ -141,12 +141,12 @@ export const IntraUnitBondCylinderParams = {
|
||||
...BondCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
|
||||
useImpostor: PD.Boolean(true),
|
||||
tryUseImpostor: PD.Boolean(true),
|
||||
};
|
||||
export type IntraUnitBondCylinderParams = typeof IntraUnitBondCylinderParams
|
||||
|
||||
export function IntraUnitBondCylinderVisual(materialId: number, props?: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) {
|
||||
return props?.useImpostor && webgl && webgl.extensions.fragDepth
|
||||
export function IntraUnitBondCylinderVisual(materialId: number, structure: Structure, props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) {
|
||||
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
|
||||
? IntraUnitBondCylinderImpostorVisual(materialId)
|
||||
: IntraUnitBondCylinderMeshVisual(materialId);
|
||||
}
|
||||
@@ -182,8 +182,8 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
|
||||
}
|
||||
}
|
||||
},
|
||||
mustRecreate: (props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return !props.useImpostor || !webgl;
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseImpostor || !webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -222,8 +222,8 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
|
||||
}
|
||||
}
|
||||
},
|
||||
mustRecreate: (props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return props.useImpostor && !!webgl;
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseImpostor && !!webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { createElementSphereImpostor, ElementIterator, getElementLoci, eachElement, createElementSphereMesh } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
|
||||
export const ElementSphereParams = {
|
||||
...UnitsMeshParams,
|
||||
@@ -19,12 +20,12 @@ export const ElementSphereParams = {
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
traceOnly: PD.Boolean(false),
|
||||
useImpostor: PD.Boolean(true),
|
||||
tryUseImpostor: PD.Boolean(true),
|
||||
};
|
||||
export type ElementSphereParams = typeof ElementSphereParams
|
||||
|
||||
export function ElementSphereVisual(materialId: number, props?: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
|
||||
return props?.useImpostor && webgl && webgl.extensions.fragDepth
|
||||
export function ElementSphereVisual(materialId: number, structure: Structure, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
|
||||
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
|
||||
? ElementSphereImpostorVisual(materialId)
|
||||
: ElementSphereMeshVisual(materialId);
|
||||
}
|
||||
@@ -42,8 +43,8 @@ export function ElementSphereImpostorVisual(materialId: number): UnitsVisual<Ele
|
||||
newProps.traceOnly !== currentProps.traceOnly
|
||||
);
|
||||
},
|
||||
mustRecreate: (props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
|
||||
return !props.useImpostor || !webgl;
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseImpostor || !webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -63,8 +64,8 @@ export function ElementSphereMeshVisual(materialId: number): UnitsVisual<Element
|
||||
newProps.traceOnly !== currentProps.traceOnly
|
||||
);
|
||||
},
|
||||
mustRecreate: (props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
|
||||
return props.useImpostor && !!webgl;
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseImpostor && !!webgl;
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -20,11 +20,12 @@ import { getStructureExtraRadius, getUnitExtraRadius } from './util/common';
|
||||
|
||||
async function createGaussianDensityVolume(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
|
||||
const { runtime, webgl } = ctx;
|
||||
if (webgl === undefined) throw new Error('createGaussianDensityVolume requires `webgl` object in VisualContext');
|
||||
if (!webgl || !webgl.extensions.blendMinMax) {
|
||||
throw new Error('GaussianDensityVolume requires `webgl` and `blendMinMax` extension');
|
||||
}
|
||||
|
||||
const p = { ...props, useGpu: true };
|
||||
const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
|
||||
const densityTextureData = await computeStructureGaussianDensityTexture(structure, p, webgl, oldTexture).runInContext(runtime);
|
||||
const densityTextureData = await computeStructureGaussianDensityTexture(structure, props, webgl, oldTexture).runInContext(runtime);
|
||||
const { transform, texture, bbox, gridDim } = densityTextureData;
|
||||
const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
|
||||
|
||||
@@ -71,11 +72,15 @@ export function GaussianDensityVolumeVisual(materialId: number): ComplexVisual<G
|
||||
|
||||
async function createUnitsGaussianDensityVolume(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
|
||||
const { runtime, webgl } = ctx;
|
||||
if (webgl === undefined) throw new Error('createUnitGaussianDensityVolume requires `webgl` object in VisualContext');
|
||||
if (!webgl) {
|
||||
// gpu gaussian density also needs blendMinMax but there is no fallback here so
|
||||
// we allow it here with the results that there is no group id assignment and
|
||||
// hence no group-based coloring or picking
|
||||
throw new Error('GaussianDensityVolume requires `webgl`');
|
||||
}
|
||||
|
||||
const p = { ...props, useGpu: true };
|
||||
const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
|
||||
const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, p, webgl, oldTexture).runInContext(runtime);
|
||||
const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, props, webgl, oldTexture).runInContext(runtime);
|
||||
const { transform, texture, bbox, gridDim } = densityTextureData;
|
||||
const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual } from '../units-visual';
|
||||
import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual, StructureGroup } from '../units-visual';
|
||||
import { GaussianDensityParams, computeUnitGaussianDensity, computeUnitGaussianDensityTexture2d, GaussianDensityProps, computeStructureGaussianDensity, computeStructureGaussianDensityTexture2d } from './util/gaussian';
|
||||
import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure } from '../../../mol-model/structure';
|
||||
@@ -15,19 +15,18 @@ import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/a
|
||||
import { ElementIterator, getElementLoci, eachElement, getSerialElementLoci, eachSerialElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { TextureMesh } from '../../../mol-geo/geometry/texture-mesh/texture-mesh';
|
||||
import { calcActiveVoxels } from '../../../mol-gl/compute/marching-cubes/active-voxels';
|
||||
import { createHistogramPyramid } from '../../../mol-gl/compute/histogram-pyramid/reduction';
|
||||
import { createIsosurfaceBuffers } from '../../../mol-gl/compute/marching-cubes/isosurface';
|
||||
import { extractIsosurface } from '../../../mol-gl/compute/marching-cubes/isosurface';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { ComplexVisual, ComplexMeshParams, ComplexMeshVisual, ComplexTextureMeshVisual, ComplexTextureMeshParams } from '../complex-visual';
|
||||
import { getUnitExtraRadius, getStructureExtraRadius } from './util/common';
|
||||
import { getUnitExtraRadius, getStructureExtraRadius, getVolumeSliceInfo } from './util/common';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
|
||||
const SharedParams = {
|
||||
...GaussianDensityParams,
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
useGpu: PD.Boolean(false),
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
};
|
||||
type SharedParams = typeof SharedParams
|
||||
|
||||
export const GaussianSurfaceMeshParams = {
|
||||
...UnitsMeshParams,
|
||||
@@ -47,23 +46,36 @@ function gpuSupport(webgl: WebGLContext) {
|
||||
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.blendMinMax && webgl.extensions.drawBuffers;
|
||||
}
|
||||
|
||||
export function GaussianSurfaceVisual(materialId: number, props?: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) {
|
||||
return props?.useGpu && webgl && gpuSupport(webgl)
|
||||
? GaussianSurfaceTextureMeshVisual(materialId)
|
||||
: GaussianSurfaceMeshVisual(materialId);
|
||||
function suitableForGpu(structure: Structure, props: PD.Values<SharedParams>, webgl: WebGLContext) {
|
||||
// lower resolutions are about as fast on CPU vs integrated GPU,
|
||||
// very low resolutions have artifacts when calculated on GPU
|
||||
if (props.resolution > 1) return false;
|
||||
// the GPU is much more memory contraint, especially true for integrated GPUs,
|
||||
// being conservative here still allows for small and medium sized assemblies
|
||||
const d = webgl.maxTextureSize / 3;
|
||||
const { areaCells, maxAreaCells } = getVolumeSliceInfo(structure.boundary.box, props.resolution, d * d);
|
||||
return areaCells < maxAreaCells;
|
||||
}
|
||||
|
||||
export function StructureGaussianSurfaceVisual(materialId: number, props?: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) {
|
||||
return props?.useGpu && webgl && gpuSupport(webgl)
|
||||
? StructureGaussianSurfaceTextureMeshVisual(materialId)
|
||||
: StructureGaussianSurfaceMeshVisual(materialId);
|
||||
export function GaussianSurfaceVisual(materialId: number, structure: Structure, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) {
|
||||
if (props.tryUseGpu && webgl && gpuSupport(webgl) && suitableForGpu(structure, props, webgl)) {
|
||||
return GaussianSurfaceTextureMeshVisual(materialId);
|
||||
}
|
||||
return GaussianSurfaceMeshVisual(materialId);
|
||||
}
|
||||
|
||||
export function StructureGaussianSurfaceVisual(materialId: number, structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) {
|
||||
if (props.tryUseGpu && webgl && gpuSupport(webgl) && suitableForGpu(structure, props, webgl)) {
|
||||
return StructureGaussianSurfaceTextureMeshVisual(materialId);
|
||||
}
|
||||
return StructureGaussianSurfaceMeshVisual(materialId);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
|
||||
const { smoothness } = props;
|
||||
const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
isoLevel: Math.exp(-smoothness) / radiusFactor,
|
||||
@@ -96,8 +108,8 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
|
||||
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
|
||||
if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
|
||||
},
|
||||
mustRecreate: (props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return props.useGpu && !!webgl;
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseGpu && !!webgl && suitableForGpu(structureGroup.structure, props, webgl);
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -106,7 +118,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
|
||||
|
||||
async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
|
||||
const { smoothness } = props;
|
||||
const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
isoLevel: Math.exp(-smoothness) / radiusFactor,
|
||||
@@ -138,8 +150,8 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
|
||||
if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
|
||||
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
|
||||
},
|
||||
mustRecreate: (props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return props.useGpu && !!webgl;
|
||||
mustRecreate: (structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseGpu && !!webgl && suitableForGpu(structure, props, webgl);
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -169,29 +181,11 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
|
||||
|
||||
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();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
|
||||
// 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();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
|
||||
const boundingSphere = Sphere3D.expand(Sphere3D(), unit.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();
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
@@ -210,12 +204,13 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
|
||||
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
|
||||
if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
|
||||
},
|
||||
mustRecreate: (props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return !props.useGpu || !webgl;
|
||||
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseGpu || !webgl || !suitableForGpu(structureGroup.structure, props, webgl);
|
||||
},
|
||||
dispose: (geometry: TextureMesh) => {
|
||||
geometry.vertexTexture.ref.value.destroy();
|
||||
geometry.groupTexture.ref.value.destroy();
|
||||
geometry.normalTexture.ref.value.destroy();
|
||||
geometry.vertexGroupTexture.ref.value.destroy();
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -243,29 +238,11 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
|
||||
|
||||
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();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
|
||||
// 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();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
|
||||
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();
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
@@ -283,12 +260,13 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
|
||||
if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
|
||||
if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
|
||||
},
|
||||
mustRecreate: (props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return !props.useGpu || !webgl;
|
||||
mustRecreate: (structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseGpu || !webgl || !suitableForGpu(structure, props, webgl);
|
||||
},
|
||||
dispose: (geometry: TextureMesh) => {
|
||||
geometry.vertexTexture.ref.value.destroy();
|
||||
geometry.groupTexture.ref.value.destroy();
|
||||
geometry.normalTexture.ref.value.destroy();
|
||||
geometry.vertexGroupTexture.ref.value.destroy();
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import { getUnitExtraRadius } from './util/common';
|
||||
|
||||
async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
|
||||
const { smoothness } = props;
|
||||
const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
|
||||
const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
|
||||
|
||||
const params = {
|
||||
isoLevel: Math.exp(-smoothness),
|
||||
|
||||
@@ -102,11 +102,24 @@ export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
|
||||
|
||||
const DefaultMaxCells = 500_000_000;
|
||||
|
||||
/** guard against overly high resolution for the given box size */
|
||||
export function getVolumeSliceInfo(box: Box3D, resolution: number, maxCells = DefaultMaxCells) {
|
||||
const size = Box3D.size(Vec3(), box);
|
||||
Vec3.ceil(size, size);
|
||||
size.sort((a, b) => b - a); // descending
|
||||
const maxAreaCells = Math.floor(Math.cbrt(maxCells) * Math.cbrt(maxCells));
|
||||
const area = size[0] * size[1];
|
||||
const areaCells = Math.ceil(area / (resolution * resolution));
|
||||
return { area, areaCells, maxAreaCells };
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard against overly high resolution for the given box size.
|
||||
* Internally it uses the largest 2d slice of the box to determine the
|
||||
* maximum resolution to account for the 2d texture layout on the GPU.
|
||||
*/
|
||||
export function ensureReasonableResolution<T>(box: Box3D, props: { resolution: number } & T, maxCells = DefaultMaxCells) {
|
||||
const volume = Box3D.volume(box);
|
||||
const approxCells = volume / props.resolution;
|
||||
const resolution = approxCells > maxCells ? volume / maxCells : props.resolution;
|
||||
const { area, areaCells, maxAreaCells } = getVolumeSliceInfo(box, props.resolution, maxCells);
|
||||
const resolution = areaCells > maxAreaCells ? Math.sqrt(area / maxAreaCells) : props.resolution;
|
||||
return { ...props, resolution };
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,16 @@ export const GaussianDensityParams = {
|
||||
export const DefaultGaussianDensityProps = PD.getDefaultValues(GaussianDensityParams);
|
||||
export type GaussianDensityProps = typeof DefaultGaussianDensityProps
|
||||
|
||||
function getTextureMaxCells(webgl: WebGLContext) {
|
||||
const d = Math.min(webgl.maxTextureSize / 2, 4096);
|
||||
return d * d;
|
||||
//
|
||||
|
||||
export function getTextureMaxCells(webgl: WebGLContext, structure?: Structure) {
|
||||
const d = webgl.maxTextureSize / 3;
|
||||
return (d * d) / Math.max(1, (structure ? structure.units.length / 16 : 1));
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) {
|
||||
export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps) {
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
@@ -41,7 +43,7 @@ export function computeUnitGaussianDensity(structure: Structure, unit: Unit, pro
|
||||
|
||||
export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
|
||||
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return GaussianDensityTexture(webgl, position, box, radius, p, texture);
|
||||
@@ -50,7 +52,7 @@ export function computeUnitGaussianDensityTexture(structure: Structure, unit: Un
|
||||
|
||||
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
|
||||
const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
|
||||
@@ -59,7 +61,7 @@ export function computeUnitGaussianDensityTexture2d(structure: Structure, unit:
|
||||
|
||||
//
|
||||
|
||||
export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps, webgl?: WebGLContext) {
|
||||
export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps) {
|
||||
const { box } = structure.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -59,9 +59,9 @@ export interface QualityProps {
|
||||
}
|
||||
|
||||
export const DefaultQualityThresholds = {
|
||||
lowestElementCount: 500_000,
|
||||
lowerElementCount: 200_000,
|
||||
lowElementCount: 60_000,
|
||||
lowestElementCount: 1_000_000,
|
||||
lowerElementCount: 500_000,
|
||||
lowElementCount: 100_000,
|
||||
mediumElementCount: 20_000,
|
||||
highElementCount: 2_000,
|
||||
coarseGrainedFactor: 10,
|
||||
@@ -129,49 +129,49 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
|
||||
detail = 3;
|
||||
radialSegments = 36;
|
||||
linearSegments = 18;
|
||||
resolution = Math.max(volume / 500_000_000, 0.1);
|
||||
resolution = 0.1;
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'higher':
|
||||
detail = 3;
|
||||
radialSegments = 28;
|
||||
linearSegments = 14;
|
||||
resolution = Math.max(volume / 400_000_000, 0.3);
|
||||
resolution = 0.3;
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'high':
|
||||
detail = 2;
|
||||
radialSegments = 20;
|
||||
linearSegments = 10;
|
||||
resolution = Math.max(volume / 300_000_000, 0.5);
|
||||
resolution = 0.5;
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'medium':
|
||||
detail = 1;
|
||||
radialSegments = 12;
|
||||
linearSegments = 8;
|
||||
resolution = Math.max(volume / 200_000_000, 1);
|
||||
resolution = 0.8;
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'low':
|
||||
detail = 0;
|
||||
radialSegments = 8;
|
||||
linearSegments = 3;
|
||||
resolution = Math.max(volume / 150_000_000, 2);
|
||||
resolution = 1.3;
|
||||
doubleSided = false;
|
||||
break;
|
||||
case 'lower':
|
||||
detail = 0;
|
||||
radialSegments = 4;
|
||||
linearSegments = 2;
|
||||
resolution = Math.max(volume / 100_000_000, 4);
|
||||
resolution = 3;
|
||||
doubleSided = false;
|
||||
break;
|
||||
case 'lowest':
|
||||
detail = 0;
|
||||
radialSegments = 2;
|
||||
linearSegments = 1;
|
||||
resolution = Math.max(volume / 75_000_000, 8);
|
||||
resolution = 8;
|
||||
doubleSided = false;
|
||||
break;
|
||||
case 'custom':
|
||||
@@ -179,6 +179,8 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
|
||||
break;
|
||||
}
|
||||
|
||||
// max resolution based on volume (for 'auto' quality)
|
||||
resolution = Math.max(resolution, volume / 500_000_000);
|
||||
resolution = Math.min(resolution, 20);
|
||||
|
||||
if ((props.alpha !== undefined && props.alpha < 1) || !!props.xrayShaded) {
|
||||
|
||||
@@ -47,7 +47,7 @@ interface Visual<D, P extends PD.Params> {
|
||||
setTransparency: (transparency: Transparency) => void
|
||||
setClipping: (clipping: Clipping) => void
|
||||
destroy: () => void
|
||||
mustRecreate?: (props: PD.Values<P>, webgl?: WebGLContext) => boolean
|
||||
mustRecreate?: (data: D, props: PD.Values<P>, webgl?: WebGLContext) => boolean
|
||||
}
|
||||
namespace Visual {
|
||||
export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) => boolean
|
||||
|
||||
@@ -21,7 +21,7 @@ import { RepresentationContext, RepresentationParamsGetter } from '../representa
|
||||
import { Interval } from '../../mol-data/int';
|
||||
import { Loci, EmptyLoci } from '../../mol-model/loci';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci } from './util';
|
||||
import { createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
|
||||
|
||||
function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
|
||||
const bbox = Box3D();
|
||||
@@ -34,6 +34,11 @@ function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
|
||||
|
||||
export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
|
||||
const gridDimension = volume.grid.cells.space.dimensions as Vec3;
|
||||
const { width, height } = getVolumeTexture2dLayout(gridDimension);
|
||||
if(Math.max(width, height) > webgl.maxTextureSize / 2) {
|
||||
throw new Error('volume too large for direct-volume rendering');
|
||||
}
|
||||
|
||||
const textureImage = createVolumeTexture2d(volume, 'normals');
|
||||
// debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
@@ -72,6 +77,10 @@ function getUnitToCartn(grid: Grid) {
|
||||
|
||||
export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
|
||||
const gridDimension = volume.grid.cells.space.dimensions as Vec3;
|
||||
if(Math.max(...gridDimension) > webgl.max3dTextureSize / 2) {
|
||||
throw new Error('volume too large for direct-volume rendering');
|
||||
}
|
||||
|
||||
const textureVolume = createVolumeTexture3d(volume);
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const bbox = getBoundingBox(gridDimension, transform);
|
||||
|
||||
@@ -24,9 +24,7 @@ import { Tensor, Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { createVolumeTexture2d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
|
||||
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
|
||||
import { calcActiveVoxels } from '../../mol-gl/compute/marching-cubes/active-voxels';
|
||||
import { createHistogramPyramid } from '../../mol-gl/compute/histogram-pyramid/reduction';
|
||||
import { createIsosurfaceBuffers } from '../../mol-gl/compute/marching-cubes/isosurface';
|
||||
import { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurface';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
@@ -41,10 +39,19 @@ function gpuSupport(webgl: WebGLContext) {
|
||||
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
|
||||
}
|
||||
|
||||
export function IsosurfaceVisual(materialId: number, props?: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) {
|
||||
return props?.useGpu && webgl && gpuSupport(webgl)
|
||||
? IsosurfaceTextureMeshVisual(materialId)
|
||||
: IsosurfaceMeshVisual(materialId);
|
||||
const Padding = 1;
|
||||
|
||||
function suitableForGpu(volume: Volume, webgl: WebGLContext) {
|
||||
const gridDim = volume.grid.cells.space.dimensions as Vec3;
|
||||
const { powerOfTwoSize } = getVolumeTexture2dLayout(gridDim, Padding);
|
||||
return powerOfTwoSize <= webgl.maxTextureSize / 2;
|
||||
}
|
||||
|
||||
export function IsosurfaceVisual(materialId: number, volume: Volume, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) {
|
||||
if (props.tryUseGpu && webgl && gpuSupport(webgl) && suitableForGpu(volume, webgl)) {
|
||||
return IsosurfaceTextureMeshVisual(materialId);
|
||||
}
|
||||
return IsosurfaceMeshVisual(materialId);
|
||||
}
|
||||
|
||||
function getLoci(volume: Volume, props: VolumeIsosurfaceProps) {
|
||||
@@ -78,7 +85,11 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol
|
||||
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
Mesh.transform(surface, transform);
|
||||
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
|
||||
if (ctx.webgl && !ctx.webgl.isWebGL2) {
|
||||
// 2nd arg means not to split triangles based on group id. Splitting triangles
|
||||
// is too expensive if each cell has its own group id as is the case here.
|
||||
Mesh.uniformTriangleGroup(surface, false);
|
||||
}
|
||||
|
||||
surface.setBoundingSphere(Volume.getBoundingSphere(volume));
|
||||
|
||||
@@ -90,7 +101,7 @@ export const IsosurfaceMeshParams = {
|
||||
...TextureMesh.Params,
|
||||
...VolumeIsosurfaceParams,
|
||||
quality: { ...Mesh.Params.quality, isEssential: false },
|
||||
useGpu: PD.Boolean(false),
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
};
|
||||
export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams
|
||||
|
||||
@@ -105,8 +116,8 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
|
||||
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
|
||||
},
|
||||
geometryUtils: Mesh.Utils,
|
||||
mustRecreate: (props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return props.useGpu && !!webgl;
|
||||
mustRecreate: (volume: Volume, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseGpu && !!webgl && suitableForGpu(volume, webgl);
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -119,26 +130,30 @@ namespace VolumeIsosurfaceTexture {
|
||||
export function get(volume: Volume, webgl: WebGLContext) {
|
||||
const { resources } = webgl;
|
||||
|
||||
const padding = 1;
|
||||
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
|
||||
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, padding);
|
||||
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
|
||||
const gridTexDim = Vec3.create(width, height, 0);
|
||||
const gridTexScale = Vec2.create(width / texDim, height / texDim);
|
||||
// console.log({ texDim, width, height, gridDimension });
|
||||
|
||||
if (texDim > webgl.maxTextureSize / 2) {
|
||||
throw new Error('volume too large for gpu isosurface extraction');
|
||||
}
|
||||
|
||||
if (!volume._propertyData[name]) {
|
||||
volume._propertyData[name] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
volume._propertyData[name] = resources.texture('image-uint8', 'alpha', 'ubyte', 'linear');
|
||||
const texture = volume._propertyData[name] as Texture;
|
||||
texture.define(texDim, texDim);
|
||||
// load volume into sub-section of texture
|
||||
texture.load(createVolumeTexture2d(volume, 'groups', padding), true);
|
||||
texture.load(createVolumeTexture2d(volume, 'data', Padding), true);
|
||||
volume.customProperties.add(descriptor);
|
||||
volume.customProperties.assets(descriptor, [{ dispose: () => texture.destroy() }]);
|
||||
}
|
||||
|
||||
gridDimension[0] += padding;
|
||||
gridDimension[1] += padding;
|
||||
gridDimension[0] += Padding;
|
||||
gridDimension[1] += Padding;
|
||||
|
||||
return {
|
||||
texture: volume._propertyData[name] as Texture,
|
||||
@@ -160,28 +175,10 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
|
||||
|
||||
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
|
||||
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx.webgl, texture, gridDimension, gridTexDim, isoLevel, gridTexScale);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
|
||||
// console.time('createHistogramPyramid');
|
||||
const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, gridTexScale, gridTexDim);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createHistogramPyramid');
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
|
||||
|
||||
// console.time('createIsosurfaceBuffers');
|
||||
const gv = createIsosurfaceBuffers(ctx.webgl, activeVoxelsTex, texture, compacted, gridDimension, gridTexDim, transform, isoLevel, textureMesh ? textureMesh.vertexGroupTexture.ref.value : undefined, textureMesh ? textureMesh.normalTexture.ref.value : undefined);
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
|
||||
// console.log({
|
||||
// renderables: ctx.webgl.namedComputeRenderables,
|
||||
// framebuffers: ctx.webgl.namedFramebuffers,
|
||||
// textures: ctx.webgl.namedTextures,
|
||||
// });
|
||||
// ctx.webgl.waitForGpuCommandsCompleteSync();
|
||||
return surface;
|
||||
}
|
||||
|
||||
@@ -196,12 +193,13 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
|
||||
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
|
||||
},
|
||||
geometryUtils: TextureMesh.Utils,
|
||||
mustRecreate: (props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return !props.useGpu || !webgl;
|
||||
mustRecreate: (volume: Volume, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseGpu || !webgl || !suitableForGpu(volume, webgl);
|
||||
},
|
||||
dispose: (geometry: TextureMesh) => {
|
||||
geometry.vertexTexture.ref.value.destroy();
|
||||
geometry.groupTexture.ref.value.destroy();
|
||||
geometry.normalTexture.ref.value.destroy();
|
||||
geometry.vertexGroupTexture.ref.value.destroy();
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
|
||||
getLoci(pickingId: PickingId, volume: Volume, props: PD.Values<P>, id: number): Loci
|
||||
eachLocation(loci: Loci, volume: Volume, props: PD.Values<P>, apply: (interval: Interval) => boolean): boolean
|
||||
setUpdateState(state: VisualUpdateState, volume: Volume, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
|
||||
mustRecreate?: (props: PD.Values<P>) => boolean
|
||||
mustRecreate?: (volume: Volume, props: PD.Values<P>) => boolean
|
||||
dispose?: (geometry: G) => void
|
||||
}
|
||||
|
||||
@@ -210,7 +210,10 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
|
||||
},
|
||||
destroy() {
|
||||
dispose?.(geometry);
|
||||
renderObject = undefined;
|
||||
if (renderObject) {
|
||||
renderObject.state.disposed = true;
|
||||
renderObject = undefined;
|
||||
}
|
||||
},
|
||||
mustRecreate
|
||||
};
|
||||
@@ -228,7 +231,7 @@ export const VolumeParams = {
|
||||
};
|
||||
export type VolumeParams = typeof VolumeParams
|
||||
|
||||
export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, P>, visualCtor: (materialId: number, props?: PD.Values<P>, webgl?: WebGLContext) => VolumeVisual<P>, getLoci: (volume: Volume, props: PD.Values<P>) => Loci): VolumeRepresentation<P> {
|
||||
export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, P>, visualCtor: (materialId: number, volume: Volume, props: PD.Values<P>, webgl?: WebGLContext) => VolumeVisual<P>, getLoci: (volume: Volume, props: PD.Values<P>) => Loci): VolumeRepresentation<P> {
|
||||
let version = 0;
|
||||
const { webgl } = ctx;
|
||||
const updated = new Subject<number>();
|
||||
@@ -253,10 +256,10 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
|
||||
|
||||
return Task.create('Creating or updating VolumeRepresentation', async runtime => {
|
||||
if (!visual) {
|
||||
visual = visualCtor(materialId, _props, webgl);
|
||||
} else if (visual.mustRecreate?.(_props, webgl)) {
|
||||
visual = visualCtor(materialId, _volume, _props, webgl);
|
||||
} else if (visual.mustRecreate?.(_volume, _props, webgl)) {
|
||||
visual.destroy();
|
||||
visual = visualCtor(materialId, _props, webgl);
|
||||
visual = visualCtor(materialId, _volume, _props, webgl);
|
||||
}
|
||||
const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, volume);
|
||||
if (promise) await promise;
|
||||
|
||||
@@ -75,13 +75,14 @@ export function getVolumeTexture2dLayout(dim: Vec3, padding = 0) {
|
||||
return { width, height, columns, rows, powerOfTwoSize: height < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
|
||||
}
|
||||
|
||||
export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'groups', padding = 0) {
|
||||
export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'groups' | 'data', padding = 0) {
|
||||
const { cells: { space, data }, stats: { max, min } } = volume.grid;
|
||||
const dim = space.dimensions as Vec3;
|
||||
const { dataOffset: o } = space;
|
||||
const { width, height } = getVolumeTexture2dLayout(dim, padding);
|
||||
|
||||
const array = new Uint8Array(width * height * 4);
|
||||
const itemSize = variant === 'data' ? 1 : 4;
|
||||
const array = new Uint8Array(width * height * itemSize);
|
||||
const textureImage = { array, width, height };
|
||||
|
||||
const diff = max - min;
|
||||
@@ -102,28 +103,32 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
|
||||
const column = Math.floor(((z * xnp) % width) / xnp);
|
||||
const row = Math.floor((z * xnp) / width);
|
||||
const px = column * xnp + x;
|
||||
const index = 4 * ((row * ynp * width) + (y * width) + px);
|
||||
const index = itemSize * ((row * ynp * width) + (y * width) + px);
|
||||
const offset = o(x, y, z);
|
||||
|
||||
if (variant === 'groups') {
|
||||
encodeFloatRGBtoArray(offset, array, index);
|
||||
if (variant === 'data') {
|
||||
array[index] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
} else {
|
||||
v3set(n0,
|
||||
data[o(Math.max(0, x - 1), y, z)],
|
||||
data[o(x, Math.max(0, y - 1), z)],
|
||||
data[o(x, y, Math.max(0, z - 1))]
|
||||
);
|
||||
v3set(n1,
|
||||
data[o(Math.min(xn1, x + 1), y, z)],
|
||||
data[o(x, Math.min(yn1, y + 1), z)],
|
||||
data[o(x, y, Math.min(zn1, z + 1))]
|
||||
);
|
||||
v3normalize(n0, v3sub(n0, n0, n1));
|
||||
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
|
||||
v3toArray(v3scale(n0, n0, 255), array, index);
|
||||
}
|
||||
if (variant === 'groups') {
|
||||
encodeFloatRGBtoArray(offset, array, index);
|
||||
} else {
|
||||
v3set(n0,
|
||||
data[o(Math.max(0, x - 1), y, z)],
|
||||
data[o(x, Math.max(0, y - 1), z)],
|
||||
data[o(x, y, Math.max(0, z - 1))]
|
||||
);
|
||||
v3set(n1,
|
||||
data[o(Math.min(xn1, x + 1), y, z)],
|
||||
data[o(x, Math.min(yn1, y + 1), z)],
|
||||
data[o(x, y, Math.min(zn1, z + 1))]
|
||||
);
|
||||
v3normalize(n0, v3sub(n0, n0, n1));
|
||||
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
|
||||
v3toArray(v3scale(n0, n0, 255), array, index);
|
||||
}
|
||||
|
||||
array[index + 3] = ((data[offset] - min) / diff) * 255;
|
||||
array[index + 3] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +172,7 @@ export function createVolumeTexture3d(volume: Volume) {
|
||||
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
|
||||
v3toArray(v3scale(n0, n0, 255), array, i);
|
||||
|
||||
array[i + 3] = ((data[offset] - min) / diff) * 255;
|
||||
array[i + 3] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ async function init() {
|
||||
console.timeEnd('gpu mc pyramid2');
|
||||
|
||||
console.time('gpu mc vert2');
|
||||
createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue);
|
||||
createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, true);
|
||||
webgl.waitForGpuCommandsCompleteSync();
|
||||
console.timeEnd('gpu mc vert2');
|
||||
console.timeEnd('gpu mc2');
|
||||
@@ -96,7 +96,7 @@ async function init() {
|
||||
console.timeEnd('gpu mc pyramid');
|
||||
|
||||
console.time('gpu mc vert');
|
||||
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue);
|
||||
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, true);
|
||||
webgl.waitForGpuCommandsCompleteSync();
|
||||
console.timeEnd('gpu mc vert');
|
||||
console.timeEnd('gpu mc');
|
||||
@@ -104,7 +104,7 @@ async function init() {
|
||||
console.log({ ...webgl.stats, programCount: webgl.stats.resourceCounts.program, shaderCount: webgl.stats.resourceCounts.shader });
|
||||
|
||||
const mcBoundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox);
|
||||
const mcIsosurface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, mcBoundingSphere);
|
||||
const mcIsosurface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, mcBoundingSphere);
|
||||
const mcIsoSurfaceProps = { doubleSided: true, flatShaded: true, alpha: 1.0 };
|
||||
const mcIsoSurfaceValues = TextureMesh.Utils.createValuesSimple(mcIsosurface, mcIsoSurfaceProps, Color(0x112299), 1);
|
||||
// console.log('mcIsoSurfaceValues', mcIsoSurfaceValues)
|
||||
|
||||
Reference in New Issue
Block a user