molstar
Version:
A comprehensive macromolecular library.
245 lines • 13.8 kB
JavaScript
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { __assign, __awaiter, __generator } from "tslib";
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Grid, Volume } from '../../mol-model/volume';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { computeMarchingCubesMesh, computeMarchingCubesLines } from '../../mol-geo/util/marching-cubes/algorithm';
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
import { LocationIterator } from '../../mol-geo/util/location-iterator';
import { NullLocation } from '../../mol-model/location';
import { Lines } from '../../mol-geo/geometry/lines/lines';
import { Representation } from '../representation';
import { EmptyLoci } from '../../mol-model/loci';
import { Interval } from '../../mol-data/int';
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 { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurface';
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
export var VolumeIsosurfaceParams = {
isoValue: Volume.IsoValueParam
};
function gpuSupport(webgl) {
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
}
var Padding = 1;
function suitableForGpu(volume, webgl) {
var gridDim = volume.grid.cells.space.dimensions;
var powerOfTwoSize = getVolumeTexture2dLayout(gridDim, Padding).powerOfTwoSize;
return powerOfTwoSize <= webgl.maxTextureSize / 2;
}
export function IsosurfaceVisual(materialId, volume, props, webgl) {
if (props.tryUseGpu && webgl && gpuSupport(webgl) && suitableForGpu(volume, webgl)) {
return IsosurfaceTextureMeshVisual(materialId);
}
return IsosurfaceMeshVisual(materialId);
}
function getLoci(volume, props) {
return Volume.Isosurface.Loci(volume, props.isoValue);
}
function getIsosurfaceLoci(pickingId, volume, props, id) {
var objectId = pickingId.objectId, groupId = pickingId.groupId;
if (id === objectId) {
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId));
}
return EmptyLoci;
}
export function eachIsosurface(loci, volume, props, apply) {
return eachVolumeLoci(loci, volume, props.isoValue, apply);
}
//
export function createVolumeIsosurfaceMesh(ctx, volume, theme, props, mesh) {
return __awaiter(this, void 0, void 0, function () {
var ids, surface, transform;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ctx.runtime.update({ message: 'Marching cubes...' });
ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
return [4 /*yield*/, computeMarchingCubesMesh({
isoLevel: Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue,
scalarField: volume.grid.cells,
idField: Tensor.create(volume.grid.cells.space, Tensor.Data1(ids))
}, mesh).runAsChild(ctx.runtime)];
case 1:
surface = _a.sent();
transform = Grid.getGridToCartesianTransform(volume.grid);
Mesh.transform(surface, transform);
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));
return [2 /*return*/, surface];
}
});
});
}
export var IsosurfaceMeshParams = __assign(__assign(__assign(__assign({}, Mesh.Params), TextureMesh.Params), VolumeIsosurfaceParams), { quality: __assign(__assign({}, Mesh.Params.quality), { isEssential: false }), tryUseGpu: PD.Boolean(true) });
export function IsosurfaceMeshVisual(materialId) {
return VolumeVisual({
defaultProps: PD.getDefaultValues(IsosurfaceMeshParams),
createGeometry: createVolumeIsosurfaceMesh,
createLocationIterator: function (volume) { return LocationIterator(volume.grid.cells.data.length, 1, 1, function () { return NullLocation; }); },
getLoci: getIsosurfaceLoci,
eachLocation: eachIsosurface,
setUpdateState: function (state, volume, newProps, currentProps) {
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats))
state.createGeometry = true;
},
geometryUtils: Mesh.Utils,
mustRecreate: function (volume, props, webgl) {
return props.tryUseGpu && !!webgl && suitableForGpu(volume, webgl);
}
}, materialId);
}
//
var VolumeIsosurfaceTexture;
(function (VolumeIsosurfaceTexture) {
var name = 'volume-isosurface-texture';
VolumeIsosurfaceTexture.descriptor = CustomPropertyDescriptor({ name: name });
function get(volume, webgl) {
var resources = webgl.resources;
var transform = Grid.getGridToCartesianTransform(volume.grid);
var gridDimension = Vec3.clone(volume.grid.cells.space.dimensions);
var _a = getVolumeTexture2dLayout(gridDimension, Padding), width = _a.width, height = _a.height, texDim = _a.powerOfTwoSize;
var gridTexDim = Vec3.create(width, height, 0);
var 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', 'alpha', 'ubyte', 'linear');
var texture_1 = volume._propertyData[name];
texture_1.define(texDim, texDim);
// load volume into sub-section of texture
texture_1.load(createVolumeTexture2d(volume, 'data', Padding), true);
volume.customProperties.add(VolumeIsosurfaceTexture.descriptor);
volume.customProperties.assets(VolumeIsosurfaceTexture.descriptor, [{ dispose: function () { return texture_1.destroy(); } }]);
}
gridDimension[0] += Padding;
gridDimension[1] += Padding;
return {
texture: volume._propertyData[name],
transform: transform,
gridDimension: gridDimension,
gridTexDim: gridTexDim,
gridTexScale: gridTexScale
};
}
VolumeIsosurfaceTexture.get = get;
})(VolumeIsosurfaceTexture || (VolumeIsosurfaceTexture = {}));
function createVolumeIsosurfaceTextureMesh(ctx, volume, theme, props, textureMesh) {
return __awaiter(this, void 0, void 0, function () {
var _a, max, min, diff, value, isoLevel, _b, texture, gridDimension, gridTexDim, gridTexScale, transform, buffer, gv, surface;
return __generator(this, function (_c) {
if (!ctx.webgl)
throw new Error('webgl context required to create volume isosurface texture-mesh');
_a = volume.grid.stats, max = _a.max, min = _a.min;
diff = max - min;
value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
isoLevel = ((value - min) / diff);
_b = VolumeIsosurfaceTexture.get(volume, ctx.webgl), texture = _b.texture, gridDimension = _b.gridDimension, gridTexDim = _b.gridTexDim, gridTexScale = _b.gridTexScale, transform = _b.transform;
buffer = textureMesh === null || textureMesh === void 0 ? void 0 : textureMesh.doubleBuffer.get();
gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, buffer === null || buffer === void 0 ? void 0 : buffer.vertex, buffer === null || buffer === void 0 ? void 0 : buffer.group, buffer === null || buffer === void 0 ? void 0 : buffer.normal);
surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
return [2 /*return*/, surface];
});
});
}
export function IsosurfaceTextureMeshVisual(materialId) {
return VolumeVisual({
defaultProps: PD.getDefaultValues(IsosurfaceMeshParams),
createGeometry: createVolumeIsosurfaceTextureMesh,
createLocationIterator: function (volume) { return LocationIterator(volume.grid.cells.data.length, 1, 1, function () { return NullLocation; }); },
getLoci: getIsosurfaceLoci,
eachLocation: eachIsosurface,
setUpdateState: function (state, volume, newProps, currentProps) {
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats))
state.createGeometry = true;
},
geometryUtils: TextureMesh.Utils,
mustRecreate: function (volume, props, webgl) {
return !props.tryUseGpu || !webgl || !suitableForGpu(volume, webgl);
},
dispose: function (geometry) {
geometry.vertexTexture.ref.value.destroy();
geometry.groupTexture.ref.value.destroy();
geometry.normalTexture.ref.value.destroy();
geometry.doubleBuffer.destroy();
}
}, materialId);
}
//
export function createVolumeIsosurfaceWireframe(ctx, volume, theme, props, lines) {
return __awaiter(this, void 0, void 0, function () {
var ids, wireframe, transform;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ctx.runtime.update({ message: 'Marching cubes...' });
ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
return [4 /*yield*/, computeMarchingCubesLines({
isoLevel: Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue,
scalarField: volume.grid.cells,
idField: Tensor.create(volume.grid.cells.space, Tensor.Data1(ids))
}, lines).runAsChild(ctx.runtime)];
case 1:
wireframe = _a.sent();
transform = Grid.getGridToCartesianTransform(volume.grid);
Lines.transform(wireframe, transform);
wireframe.setBoundingSphere(Volume.getBoundingSphere(volume));
return [2 /*return*/, wireframe];
}
});
});
}
export var IsosurfaceWireframeParams = __assign(__assign(__assign({}, Lines.Params), VolumeIsosurfaceParams), { quality: __assign(__assign({}, Lines.Params.quality), { isEssential: false }), sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }) });
export function IsosurfaceWireframeVisual(materialId) {
return VolumeVisual({
defaultProps: PD.getDefaultValues(IsosurfaceWireframeParams),
createGeometry: createVolumeIsosurfaceWireframe,
createLocationIterator: function (volume) { return LocationIterator(volume.grid.cells.data.length, 1, 1, function () { return NullLocation; }); },
getLoci: getIsosurfaceLoci,
eachLocation: eachIsosurface,
setUpdateState: function (state, volume, newProps, currentProps) {
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats))
state.createGeometry = true;
},
geometryUtils: Lines.Utils
}, materialId);
}
//
var IsosurfaceVisuals = {
'solid': function (ctx, getParams) { return VolumeRepresentation('Isosurface mesh', ctx, getParams, IsosurfaceVisual, getLoci); },
'wireframe': function (ctx, getParams) { return VolumeRepresentation('Isosurface wireframe', ctx, getParams, IsosurfaceWireframeVisual, getLoci); },
};
export var IsosurfaceParams = __assign(__assign(__assign({}, IsosurfaceMeshParams), IsosurfaceWireframeParams), { visuals: PD.MultiSelect(['solid'], PD.objectToOptions(IsosurfaceVisuals)) });
export function getIsosurfaceParams(ctx, volume) {
var p = PD.clone(IsosurfaceParams);
p.isoValue = Volume.createIsoValueParam(Volume.IsoValue.relative(2), volume.grid.stats);
return p;
}
export function IsosurfaceRepresentation(ctx, getParams) {
return Representation.createMulti('Isosurface', ctx, getParams, Representation.StateBuilder, IsosurfaceVisuals);
}
export var IsosurfaceRepresentationProvider = VolumeRepresentationProvider({
name: 'isosurface',
label: 'Isosurface',
description: 'Displays a triangulated isosurface of volumetric data.',
factory: IsosurfaceRepresentation,
getParams: getIsosurfaceParams,
defaultValues: PD.getDefaultValues(IsosurfaceParams),
defaultColorTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: function (volume) { return !Volume.isEmpty(volume); }
});
//# sourceMappingURL=isosurface.js.map