Compare commits

...

28 Commits

Author SHA1 Message Date
Alexander Rose
c6f61ea06b 1.3.2 2021-03-18 22:37:34 -07:00
Alexander Rose
3095754817 add missing PRO to standard components 2021-03-18 22:23:50 -07:00
dsehnal
c76c433410 Fix getSymmetryOperatorRef indexing 2021-03-18 22:22:56 -07:00
dsehnal
347ef3ea7a Fix createModelProperty.isApplicable 2021-03-18 22:21:57 -07:00
Tomas Kulhanek
84c47738ac FIX molstar/molstar#147 offsetWidth/offsetHeight is correct size of element when css transform:scale is used 2021-03-18 22:21:00 -07:00
Alexander Rose
fe331ead80 1.3.1 2021-02-13 23:18:36 -08:00
Alexander Rose
77d013b775 webgl, ensure active attribute with divisor 0
- workaround for FF <85
- needed for `texture-mesh` geometry
2021-02-13 11:30:13 -08:00
Alexander Rose
02a466e8b9 only update repr visibility when changed
- avoids superfluous scene rendering, e.g., when animating
2021-02-13 11:27:44 -08:00
Alexander Rose
3cb65cbe3d reduce ssao quality defaults a bit
- less texture fetches
2021-02-13 11:24:07 -08:00
Alexander Rose
fe8838542c 1.3.0 2021-02-07 13:29:46 -08:00
Alexander Rose
78b5c9aac4 Merge pull request #129 from molstar/gpu
Gpu acceleration for isosurface & gaussian-surface
2021-02-07 13:25:22 -08:00
Alexander Rose
021fa7b79b clearer param names for using gpu/impostors 2021-02-07 13:23:36 -08:00
Alexander Rose
0443589b09 allow gaussian volume without blendMinMax 2021-02-07 13:18:24 -08:00
Alexander Rose
415288de9f tweak quality settings
- lower resolution
2021-02-07 12:52:10 -08:00
Alexander Rose
ecbafb086a fix mc example 2021-02-06 12:46:25 -08:00
Alexander Rose
e5dae6c0dd increased default quality for larger structures
- rational is that larger structures can take a bit longer to create
2021-02-06 12:45:00 -08:00
Alexander Rose
16f4524bdb improved gpu support for representations
- enabled in volume isosurface & structure gaussian surface
- only if suitable (check memory requirements and resolution)
- falls back to cpu code
2021-02-06 12:43:24 -08:00
Alexander Rose
6b33021f43 fix webgl stats on render-item disposal 2021-02-06 12:30:12 -08:00
Alexander Rose
fdf37100c2 improved gpu-mc
- lower memory usage
- support for 2^32 vertices in webgl2
- fix rounding issue when creating volume texture
2021-02-06 12:28:52 -08:00
Alexander Rose
e28674d0dc model format improvements
- add a source format to the mmcif format
- add pdb format
- allow undefined in typeguard .is helpers
2021-02-03 19:36:01 -08:00
Alexander Rose
fb7456286a fix label_seq_id assignment when undefined column 2021-02-03 19:32:20 -08:00
Alexander Rose
9d240f8928 allow views of undefined columns 2021-02-03 19:30:14 -08:00
David Sehnal
48ef5efb21 Merge pull request #127 from molstar/pp-res
lower resolution ssao
2021-02-01 12:59:53 +01:00
Alexander Rose
52b2e7c144 lower res ssao 2021-01-31 13:22:22 -08:00
Alexander Rose
f2d1d60f6b fix cellpack & unit creation issues
- cellpack generate color theme can be applied to 1 model trajectories
- don't clone supplied props in Unit.create
- fix cellpack structure building to share unit.props
2021-01-31 12:06:40 -08:00
dsehnal
5a176a378a 1.2.15 2021-01-31 18:32:08 +01:00
dsehnal
60151c2c24 fix getUnitsSortedByVolume 2021-01-31 18:30:13 +01:00
dsehnal
a5db6350a2 only normalize Canvas3D props when loading from a saved state 2021-01-31 18:01:42 +01:00
71 changed files with 783 additions and 564 deletions

4
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "molstar",
"version": "1.2.14",
"version": "1.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.2.14",
"version": "1.2.15",
"license": "MIT",
"dependencies": {
"@types/argparse": "^1.0.38",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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),
};
@@ -109,11 +109,10 @@ export namespace TextureMesh {
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) {

View File

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

View File

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

View File

@@ -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()),

View File

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

View File

@@ -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>
*/
@@ -7,17 +7,16 @@
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema, AttributeSpec } from './schema';
import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema } from './schema';
import { MeshShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
export const TextureMeshSchema = {
...BaseSchema,
aGroup: AttributeSpec('float32', 1, 0),
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'),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -188,6 +188,7 @@ export interface WebGLContext {
readonly resources: WebGLResources
readonly maxTextureSize: number
readonly max3dTextureSize: number
readonly maxRenderbufferSize: number
readonly maxDrawBuffers: number
readonly maxTextureImageUnits: number
@@ -223,6 +224,7 @@ 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: extensions.drawBuffers ? gl.getParameter(extensions.drawBuffers.MAX_DRAW_BUFFERS) as number : 0,
maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number,
@@ -289,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; },

View File

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

View File

@@ -57,6 +57,7 @@ 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':
if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;

View File

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

View File

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

View File

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

View File

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

View File

@@ -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' },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -431,17 +431,23 @@ function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
return units[i].id - units[j].id;
}
function cmpUnitsVolume(units: ArrayLike<Unit>, i: number, j: number) {
return Box3D.volume(units[i].boundary.box) - Box3D.volume(units[i].boundary.box);
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 { units } = structure;
const unitsByVolume: Unit[] = [];
for (let i = 0, _i = units.length; i < _i; i++) {
unitsByVolume[i] = units[i];
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 sort(unitsByVolume, 0, unitsByVolume.length, cmpUnitsVolume, arraySwap);
return ret;
}
function getModels(s: Structure) {
@@ -1159,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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -102,13 +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 ensureReasonableResolution<T>(box: Box3D, props: { resolution: number } & T, maxCells = DefaultMaxCells) {
export function getVolumeSliceInfo(box: Box3D, resolution: number, maxCells = DefaultMaxCells) {
const size = Box3D.size(Vec3(), box);
const maxArea = Math.floor(Math.cbrt(maxCells) * Math.cbrt(maxCells));
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 maxAreaCells = Math.ceil(area / props.resolution);
const resolution = maxAreaCells > maxArea ? area / maxArea : props.resolution;
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 { area, areaCells, maxAreaCells } = getVolumeSliceInfo(box, props.resolution, maxCells);
const resolution = areaCells > maxAreaCells ? Math.sqrt(area / maxAreaCells) : props.resolution;
return { ...props, resolution };
}

View File

@@ -25,21 +25,16 @@ export type GaussianDensityProps = typeof DefaultGaussianDensityProps
//
function getTextureMaxCells(webgl: WebGLContext) {
const d = Math.min(webgl.maxTextureSize / 2, 4096);
return d * d;
}
function largestUnitBox(structure: Structure) {
const units = structure.unitsSortedByVolume;
return units[units.length - 1].lookup3d.boundary.box;
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(largestUnitBox(structure), props);
const p = ensureReasonableResolution(box, props);
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
return Task.create('Gaussian Density', async ctx => {
return await GaussianDensityCPU(ctx, position, box, radius, p);
@@ -48,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(largestUnitBox(structure), 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);
@@ -57,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(largestUnitBox(structure), 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);
@@ -66,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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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