mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Merge pull request #1708 from molstar/volume-improvements
Volume improvements
This commit is contained in:
@@ -18,6 +18,10 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Fix `direct-volume` not drawn in illumination mode
|
||||
- Fix default trackball animated spin speed
|
||||
- Use `PluginCommands` to set canvas3d props in camera behavior
|
||||
- Volume improvements
|
||||
- Add `Volume.periodicity`
|
||||
- Wrap isosurfaces for periodic volumes
|
||||
- Fix dimensions for slices
|
||||
- Add support for Input Method Editor (IME) to text params input
|
||||
- Update `guessCifVariant` to detect density files not generated by the VolumeServer
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2025 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 * as fs from 'fs';
|
||||
@@ -38,7 +39,7 @@ function print(volume: Volume) {
|
||||
}
|
||||
|
||||
async function doMesh(volume: Volume, filename: string) {
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5), wrap: 'auto' })).run();
|
||||
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
|
||||
|
||||
// Export the mesh in OBJ format.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -38,6 +38,7 @@ const IsosurfaceSchema = {
|
||||
|
||||
uGridDim: UniformSpec('v3'),
|
||||
uGridTexDim: UniformSpec('v3'),
|
||||
uGridDataDim: UniformSpec('v3'),
|
||||
uGridTransform: UniformSpec('m4'),
|
||||
uGridTransformAdjoint: UniformSpec('m3'),
|
||||
uScale: UniformSpec('v2'),
|
||||
@@ -54,7 +55,7 @@ function valueChannel(ctx: WebGLContext, volumeData: Texture) {
|
||||
return isWebGL2(ctx.gl) && volumeData.format === ctx.gl.RED ? 'red' : 'alpha';
|
||||
}
|
||||
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
|
||||
|
||||
@@ -71,6 +72,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
|
||||
ValueCell.update(v.uGridDim, gridDim);
|
||||
ValueCell.update(v.uGridTexDim, gridTexDim);
|
||||
ValueCell.update(v.uGridDataDim, gridDataDim);
|
||||
ValueCell.update(v.uGridTransform, transform);
|
||||
ValueCell.update(v.uGridTransformAdjoint, Mat3.adjointFromMat4(Mat3(), transform));
|
||||
ValueCell.update(v.uScale, scale);
|
||||
@@ -81,12 +83,12 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
|
||||
ctx.namedComputeRenderables[IsosurfaceName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, gridDataDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
|
||||
}
|
||||
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, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) {
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) {
|
||||
// console.log('uSize', Math.pow(2, levels))
|
||||
const values: IsosurfaceValues = {
|
||||
...QuadValues,
|
||||
@@ -105,6 +107,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
|
||||
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
uGridDataDim: ValueCell.create(gridDataDim),
|
||||
uGridTransform: ValueCell.create(transform),
|
||||
uGridTransformAdjoint: ValueCell.create(Mat3.adjointFromMat4(Mat3(), transform)),
|
||||
uScale: ValueCell.create(scale),
|
||||
@@ -132,7 +135,7 @@ 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, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
const { drawBuffers } = ctx.extensions;
|
||||
if (!drawBuffers) throw new Error('need WebGL draw buffers');
|
||||
|
||||
@@ -189,7 +192,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
groupTexture.attachFramebuffer(framebuffer, 1);
|
||||
normalTexture.attachFramebuffer(framebuffer, 2);
|
||||
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, gridDataDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
|
||||
framebuffer.bind();
|
||||
@@ -225,11 +228,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
*
|
||||
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
|
||||
*/
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridDataDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
if (isTimingMode) ctx.timer.mark('extractIsosurface');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, gridDataDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
|
||||
if (isTimingMode) ctx.timer.markEnd('extractIsosurface');
|
||||
|
||||
return gv;
|
||||
|
||||
@@ -22,6 +22,7 @@ uniform bool uInvert;
|
||||
|
||||
uniform vec3 uGridDim;
|
||||
uniform vec3 uGridTexDim;
|
||||
uniform vec3 uGridDataDim;
|
||||
uniform mat4 uGridTransform;
|
||||
uniform mat3 uGridTransformAdjoint;
|
||||
|
||||
@@ -93,20 +94,19 @@ vec4 baseVoxel(vec2 pos) {
|
||||
}
|
||||
|
||||
vec4 getGroup(const in vec3 p) {
|
||||
vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
|
||||
// note that we swap x and z because the texture is flipped around y
|
||||
#if defined(dAxisOrder_012)
|
||||
float group = p.z + p.y * gridDim.z + p.x * gridDim.z * gridDim.y; // 210
|
||||
float group = p.z + p.y * uGridDataDim.z + p.x * uGridDataDim.z * uGridDataDim.y; // 210
|
||||
#elif defined(dAxisOrder_021)
|
||||
float group = p.y + p.z * gridDim.y + p.x * gridDim.y * gridDim.z; // 120
|
||||
float group = p.y + p.z * uGridDataDim.y + p.x * uGridDataDim.y * uGridDataDim.z; // 120
|
||||
#elif defined(dAxisOrder_102)
|
||||
float group = p.z + p.x * gridDim.z + p.y * gridDim.z * gridDim.x; // 201
|
||||
float group = p.z + p.x * uGridDataDim.z + p.y * uGridDataDim.z * uGridDataDim.x; // 201
|
||||
#elif defined(dAxisOrder_120)
|
||||
float group = p.x + p.z * gridDim.x + p.y * gridDim.x * gridDim.z; // 021
|
||||
float group = p.x + p.z * uGridDataDim.x + p.y * uGridDataDim.x * uGridDataDim.z; // 021
|
||||
#elif defined(dAxisOrder_201)
|
||||
float group = p.y + p.x * gridDim.y + p.z * gridDim.y * gridDim.x; // 102
|
||||
float group = p.y + p.x * uGridDataDim.y + p.z * uGridDataDim.y * uGridDataDim.x; // 102
|
||||
#elif defined(dAxisOrder_210)
|
||||
float group = p.x + p.y * gridDim.x + p.z * gridDim.x * gridDim.y; // 012
|
||||
float group = p.x + p.y * uGridDataDim.x + p.z * uGridDataDim.x * uGridDataDim.y; // 012
|
||||
#endif
|
||||
return vec4(group > 16777215.5 ? vec3(1.0) : packIntToRGB(group), 1.0);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2025 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>
|
||||
@@ -36,6 +36,7 @@ export type DensityTextureData = {
|
||||
bbox: Box3D,
|
||||
gridDim: Vec3,
|
||||
gridTexDim: Vec3
|
||||
gridDataDim: Vec3
|
||||
gridTexScale: Vec2
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
@@ -99,8 +99,8 @@ export function GaussianDensityTexture3d(webgl: WebGLContext, position: Position
|
||||
return finalizeGaussianDensityTexture(data);
|
||||
}
|
||||
|
||||
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius }: _GaussianDensityTextureData): GaussianDensityTextureData {
|
||||
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridDataDim, gridTexScale, radiusFactor, resolution, maxRadius }: _GaussianDensityTextureData): GaussianDensityTextureData {
|
||||
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridDataDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
function getTransform(scale: Vec3, bbox: Box3D) {
|
||||
@@ -118,6 +118,7 @@ type _GaussianDensityTextureData = {
|
||||
bbox: Box3D,
|
||||
gridDim: Vec3,
|
||||
gridTexDim: Vec3
|
||||
gridDataDim: Vec3
|
||||
gridTexScale: Vec2
|
||||
radiusFactor: number
|
||||
resolution: number
|
||||
@@ -206,7 +207,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
|
||||
// printTextureImage(readTexture(webgl, minDistTex), { scale: 0.75 });
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridDataDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
|
||||
@@ -262,7 +263,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
|
||||
setupGroupIdRendering(webgl, renderable);
|
||||
render(texture, false);
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridDataDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace Mat3 {
|
||||
}
|
||||
|
||||
export function hasNaN(m: Mat3) {
|
||||
for (let i = 0; i < 9; i++) if (isNaN(m[i])) return true;
|
||||
for (let i = 0; i < 9; i++) if (Number.isNaN(m[i])) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Mat4 {
|
||||
}
|
||||
|
||||
export function hasNaN(m: Mat4) {
|
||||
for (let i = 0; i < 16; i++) if (isNaN(m[i])) return true;
|
||||
for (let i = 0; i < 16; i++) if (Number.isNaN(m[i])) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Quat {
|
||||
}
|
||||
|
||||
export function hasNaN(q: Quat) {
|
||||
return isNaN(q[0]) || isNaN(q[1]) || isNaN(q[2]) || isNaN(q[3]);
|
||||
return Number.isNaN(q[0]) || Number.isNaN(q[1]) || Number.isNaN(q[2]) || Number.isNaN(q[3]);
|
||||
}
|
||||
|
||||
export function create(x: number, y: number, z: number, w: number) {
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Vec2 {
|
||||
}
|
||||
|
||||
export function hasNaN(a: Vec2) {
|
||||
return isNaN(a[0]) || isNaN(a[1]);
|
||||
return Number.isNaN(a[0]) || Number.isNaN(a[1]);
|
||||
}
|
||||
|
||||
export function toArray<T extends NumberArray>(a: Vec2, out: T, offset: number) {
|
||||
|
||||
@@ -52,8 +52,12 @@ export namespace Vec3 {
|
||||
return _isFinite(a[0]) && _isFinite(a[1]) && _isFinite(a[2]);
|
||||
}
|
||||
|
||||
export function isInteger(a: Vec3): boolean {
|
||||
return Number.isInteger(a[0]) && Number.isInteger(a[1]) && Number.isInteger(a[2]);
|
||||
}
|
||||
|
||||
export function hasNaN(a: Vec3) {
|
||||
return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]);
|
||||
return Number.isNaN(a[0]) || Number.isNaN(a[1]) || Number.isNaN(a[2]);
|
||||
}
|
||||
|
||||
export function setNaN(out: Vec3) {
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Vec4 {
|
||||
}
|
||||
|
||||
export function hasNaN(a: Vec4) {
|
||||
return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]) || isNaN(a[3]);
|
||||
return Number.isNaN(a[0]) || Number.isNaN(a[1]) || Number.isNaN(a[2]) || Number.isNaN(a[3]);
|
||||
}
|
||||
|
||||
export function toArray<T extends NumberArray>(a: Vec4, out: T, offset: number) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Grid, Volume } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
@@ -71,19 +71,22 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
|
||||
// always calculate stats when all stats related values are zero
|
||||
const calcStats = header.AMIN === 0 && header.AMAX === 0 && header.AMEAN === 0 && header.ARMS === 0;
|
||||
|
||||
const volgrid: Grid = {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3(), origin_frac, dimensions_frac)) },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: (Number.isNaN(header.AMIN) || calcStats) ? arrayMin(values) : header.AMIN,
|
||||
max: (Number.isNaN(header.AMAX) || calcStats) ? arrayMax(values) : header.AMAX,
|
||||
mean: (Number.isNaN(header.AMEAN) || calcStats) ? arrayMean(values) : header.AMEAN,
|
||||
sigma: (Number.isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
entryId: params?.entryId,
|
||||
grid: {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: (isNaN(header.AMIN) || calcStats) ? arrayMin(values) : header.AMIN,
|
||||
max: (isNaN(header.AMAX) || calcStats) ? arrayMax(values) : header.AMAX,
|
||||
mean: (isNaN(header.AMEAN) || calcStats) ? arrayMean(values) : header.AMEAN,
|
||||
sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
|
||||
},
|
||||
},
|
||||
periodicity: Vec3.isInteger(dimensions_frac) ? 'xyz' : 'none',
|
||||
grid: volgrid,
|
||||
instances: [{ transform: Mat4.identity() }],
|
||||
sourceData: Ccp4Format.create(source),
|
||||
customProperties: new CustomProperties(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2025 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 { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
|
||||
@@ -38,6 +39,7 @@ export function volumeFromDensityServerData(source: DensityServer_Data_Database,
|
||||
return {
|
||||
label: params?.label,
|
||||
entryId: params?.entryId,
|
||||
periodicity: Vec3.isInteger(dimensions) ? 'xyz' : 'none',
|
||||
grid: {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
|
||||
cells: data,
|
||||
|
||||
@@ -36,6 +36,7 @@ export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, la
|
||||
return {
|
||||
label: params?.label,
|
||||
entryId: params?.entryId,
|
||||
periodicity: Vec3.isInteger(dimensions_frac) ? 'xyz' : 'none',
|
||||
grid: {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
cells: data,
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Grid {
|
||||
|
||||
export type Transform = { kind: 'spacegroup', cell: SpacegroupCell, fractionalBox: Box3D } | { kind: 'matrix', matrix: Mat4 }
|
||||
|
||||
const _scale = Mat4.zero(), _translate = Mat4.zero();
|
||||
const _scale = Mat4(), _translate = Mat4();
|
||||
export function getGridToCartesianTransform(grid: Grid) {
|
||||
if (grid.transform.kind === 'matrix') {
|
||||
return Mat4.copy(Mat4(), grid.transform.matrix);
|
||||
@@ -39,9 +39,9 @@ namespace Grid {
|
||||
|
||||
if (grid.transform.kind === 'spacegroup') {
|
||||
const { cells: { space } } = grid;
|
||||
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
|
||||
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3(), Box3D.size(Vec3(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
|
||||
const translate = Mat4.fromTranslation(_translate, grid.transform.fractionalBox.min);
|
||||
return Mat4.mul3(Mat4.zero(), grid.transform.cell.fromFractional, translate, scale);
|
||||
return Mat4.mul3(Mat4(), grid.transform.cell.fromFractional, translate, scale);
|
||||
}
|
||||
|
||||
return Mat4.identity();
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface Volume {
|
||||
transform: Mat4
|
||||
}>
|
||||
readonly sourceData: ModelFormat
|
||||
readonly periodicity?: 'none' | 'xyz'
|
||||
|
||||
// TODO use...
|
||||
customProperties: CustomProperties
|
||||
|
||||
@@ -247,7 +247,7 @@ function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, struct
|
||||
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
|
||||
|
||||
const buffer = textureMesh?.doubleBuffer.get();
|
||||
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridDataDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
if (isTimingMode) webgl.timer.markEnd('createGaussianSurfaceTextureMesh');
|
||||
|
||||
const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, densityTextureData.maxRadius);
|
||||
@@ -333,7 +333,7 @@ function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, structure
|
||||
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
|
||||
|
||||
const buffer = textureMesh?.doubleBuffer.get();
|
||||
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
const gv = extractIsosurface(webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridDataDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
if (isTimingMode) webgl.timer.markEnd('createStructureGaussianSurfaceTextureMesh');
|
||||
|
||||
const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, densityTextureData.maxRadius);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Interval, OrderedSet } from '../../mol-data/int';
|
||||
import { Tensor, Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { createVolumeCellLocationIterator, createVolumeTexture2d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
|
||||
import { createVolumeCellLocationIterator, createVolumeTexture2d, createWrappedVolume, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
|
||||
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
|
||||
import { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurface';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -31,22 +31,29 @@ import { ValueCell } from '../../mol-util/value-cell';
|
||||
|
||||
export const VolumeIsosurfaceParams = {
|
||||
isoValue: Volume.IsoValueParam,
|
||||
wrap: PD.Select('auto', PD.arrayToOptions(['off', 'on', 'auto'] as const)),
|
||||
};
|
||||
export type VolumeIsosurfaceParams = typeof VolumeIsosurfaceParams
|
||||
export type VolumeIsosurfaceProps = PD.Values<VolumeIsosurfaceParams>
|
||||
|
||||
export const VolumeIsosurfaceTextureParams = {
|
||||
isoValue: Volume.IsoValueParam,
|
||||
...VolumeIsosurfaceParams,
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
gpuDataType: PD.Select('byte', PD.arrayToOptions(['byte', 'float', 'halfFloat'] as const), { hideIf: p => !p.tryUseGpu }),
|
||||
};
|
||||
export type VolumeIsosurfaceGpuParams = typeof VolumeIsosurfaceTextureParams
|
||||
export type VolumeIsosurfaceGpuProps = PD.Values<VolumeIsosurfaceGpuParams>
|
||||
export type VolumeIsosurfaceTextureParams = typeof VolumeIsosurfaceTextureParams
|
||||
export type VolumeIsosurfaceTextureProps = PD.Values<VolumeIsosurfaceTextureParams>
|
||||
|
||||
function gpuSupport(webgl: WebGLContext) {
|
||||
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
|
||||
}
|
||||
|
||||
function shouldWrap(volume: Volume, wrap: VolumeIsosurfaceProps['wrap']) {
|
||||
if (wrap === 'on') return true;
|
||||
if (wrap === 'off') return false;
|
||||
return volume.periodicity === 'xyz';
|
||||
}
|
||||
|
||||
const Padding = 1;
|
||||
|
||||
function suitableForGpu(volume: Volume, webgl: WebGLContext) {
|
||||
@@ -97,12 +104,17 @@ export function eachIsosurface(loci: Loci, volume: Volume, key: number, props: V
|
||||
export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceProps, mesh?: Mesh) {
|
||||
ctx.runtime.update({ message: 'Marching cubes...' });
|
||||
|
||||
let cells = volume.grid.cells;
|
||||
if (shouldWrap(volume, props.wrap)) {
|
||||
cells = createWrappedVolume(volume).grid.cells;
|
||||
}
|
||||
|
||||
const ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
|
||||
|
||||
const surface = await 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))
|
||||
scalarField: cells,
|
||||
idField: Tensor.create(cells.space, Tensor.Data1(ids))
|
||||
}, mesh).runAsChild(ctx.runtime);
|
||||
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
@@ -138,7 +150,10 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
|
||||
getLoci: getIsosurfaceLoci,
|
||||
eachLocation: eachIsosurface,
|
||||
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
|
||||
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
|
||||
state.createGeometry = (
|
||||
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
|
||||
newProps.wrap !== currentProps.wrap
|
||||
);
|
||||
},
|
||||
geometryUtils: Mesh.Utils,
|
||||
mustRecreate: (volumekey: VolumeKey, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
@@ -155,11 +170,15 @@ namespace VolumeIsosurfaceTexture {
|
||||
export function clear(volume: Volume) {
|
||||
delete volume._propertyData[name];
|
||||
}
|
||||
export function get(volume: Volume, webgl: WebGLContext, props: VolumeIsosurfaceGpuProps) {
|
||||
export function get(volume: Volume, webgl: WebGLContext, props: VolumeIsosurfaceTextureProps) {
|
||||
const { gpuDataType } = props;
|
||||
const wrap = shouldWrap(volume, props.wrap);
|
||||
|
||||
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 gridTexDim = Vec3.create(width, height, 0);
|
||||
const gridDataDim = Vec3.subScalar(Vec3(), gridDimension, wrap ? 1 : 0);
|
||||
const gridTexScale = Vec2.create(width / texDim, height / texDim);
|
||||
// console.log({ texDim, width, height, gridDimension });
|
||||
|
||||
@@ -167,15 +186,15 @@ namespace VolumeIsosurfaceTexture {
|
||||
throw new Error('volume too large for gpu isosurface extraction');
|
||||
}
|
||||
|
||||
const dataType = props.gpuDataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : props.gpuDataType;
|
||||
const dataType = gpuDataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : gpuDataType;
|
||||
|
||||
if (volume._propertyData[name]?.dataType !== dataType) {
|
||||
if (volume._propertyData[name]?.dataType !== dataType || volume._propertyData[name]?.wrap !== wrap) {
|
||||
const texture = dataType === 'byte'
|
||||
? webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'linear')
|
||||
: dataType === 'halfFloat'
|
||||
? webgl.resources.texture('image-float16', 'alpha', 'fp16', 'linear')
|
||||
: webgl.resources.texture('image-float32', 'alpha', 'float', 'linear');
|
||||
volume._propertyData[name] = { texture, dataType };
|
||||
volume._propertyData[name] = { texture, dataType, wrap };
|
||||
texture.define(texDim, texDim);
|
||||
// load volume into sub-section of texture
|
||||
texture.load(createVolumeTexture2d(volume, 'data', Padding, dataType), true);
|
||||
@@ -191,12 +210,13 @@ namespace VolumeIsosurfaceTexture {
|
||||
transform,
|
||||
gridDimension,
|
||||
gridTexDim,
|
||||
gridDataDim,
|
||||
gridTexScale
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceGpuProps, textureMesh?: TextureMesh) {
|
||||
function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceTextureProps, textureMesh?: TextureMesh) {
|
||||
const { webgl } = ctx;
|
||||
if (!webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
|
||||
|
||||
@@ -204,6 +224,10 @@ function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, k
|
||||
return TextureMesh.createEmpty(textureMesh);
|
||||
}
|
||||
|
||||
if (shouldWrap(volume, props.wrap)) {
|
||||
volume = createWrappedVolume(volume);
|
||||
}
|
||||
|
||||
const { max, min } = volume.grid.stats;
|
||||
const diff = max - min;
|
||||
const value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
|
||||
@@ -214,10 +238,10 @@ function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, k
|
||||
const boundingSphere = Volume.getBoundingSphere(volume); // getting isosurface bounding-sphere is too expensive here
|
||||
|
||||
const create = (textureMesh?: TextureMesh) => {
|
||||
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, webgl, props);
|
||||
const { texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, webgl, props);
|
||||
|
||||
const buffer = textureMesh?.doubleBuffer.get();
|
||||
const gv = extractIsosurface(webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
const gv = extractIsosurface(webgl, texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
|
||||
return TextureMesh.create(gv.vertexCount, groupCount, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
|
||||
};
|
||||
@@ -240,8 +264,11 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
|
||||
getLoci: getIsosurfaceLoci,
|
||||
eachLocation: eachIsosurface,
|
||||
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
|
||||
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
|
||||
if (newProps.gpuDataType !== currentProps.gpuDataType) state.createGeometry = true;
|
||||
state.createGeometry = (
|
||||
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
|
||||
newProps.gpuDataType !== currentProps.gpuDataType ||
|
||||
newProps.wrap !== currentProps.wrap
|
||||
);
|
||||
},
|
||||
geometryUtils: TextureMesh.Utils,
|
||||
mustRecreate: (volumeKey: VolumeKey, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
@@ -261,12 +288,17 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
|
||||
export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceProps, lines?: Lines) {
|
||||
ctx.runtime.update({ message: 'Marching cubes...' });
|
||||
|
||||
let cells = volume.grid.cells;
|
||||
if (shouldWrap(volume, props.wrap)) {
|
||||
cells = createWrappedVolume(volume).grid.cells;
|
||||
}
|
||||
|
||||
const ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
|
||||
|
||||
const wireframe = await 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))
|
||||
scalarField: cells,
|
||||
idField: Tensor.create(cells.space, Tensor.Data1(ids))
|
||||
}, lines).runAsChild(ctx.runtime);
|
||||
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
@@ -293,7 +325,10 @@ export function IsosurfaceWireframeVisual(materialId: number): VolumeVisual<Isos
|
||||
getLoci: getIsosurfaceLoci,
|
||||
eachLocation: eachIsosurface,
|
||||
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {
|
||||
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
|
||||
state.createGeometry = (
|
||||
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
|
||||
newProps.wrap !== currentProps.wrap
|
||||
);
|
||||
},
|
||||
geometryUtils: Lines.Utils
|
||||
}, materialId);
|
||||
|
||||
@@ -222,6 +222,7 @@ function getSegmentTexture(volume: Volume, segment: Volume.SegmentIndex, webgl:
|
||||
const gridDimension = Box3D.size(Vec3(), bbox);
|
||||
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
|
||||
const gridTexDim = Vec3.create(width, height, 0);
|
||||
const gridDataDim = Vec3.clone(gridDimension);
|
||||
const gridTexScale = Vec2.create(width / texDim, height / texDim);
|
||||
// console.log({ texDim, width, height, gridDimension });
|
||||
|
||||
@@ -247,6 +248,7 @@ function getSegmentTexture(volume: Volume, segment: Volume.SegmentIndex, webgl:
|
||||
transform,
|
||||
gridDimension,
|
||||
gridTexDim,
|
||||
gridDataDim,
|
||||
gridTexScale
|
||||
};
|
||||
}
|
||||
@@ -258,11 +260,11 @@ async function createVolumeSegmentTextureMesh(ctx: VisualContext, volume: Volume
|
||||
return TextureMesh.createEmpty(textureMesh);
|
||||
}
|
||||
|
||||
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = getSegmentTexture(volume, segment, ctx.webgl);
|
||||
const { texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform } = getSegmentTexture(volume, segment, ctx.webgl);
|
||||
|
||||
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
|
||||
const buffer = textureMesh?.doubleBuffer.get();
|
||||
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, 0.5, false, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridDataDim, gridTexScale, transform, 0.5, false, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal);
|
||||
|
||||
const groupCount = volume.grid.cells.data.length;
|
||||
const instances = Interval.ofLength(volume.instances.length as Volume.InstanceIndex);
|
||||
|
||||
@@ -92,9 +92,9 @@ function getFrame(volume: Volume, props: SliceProps) {
|
||||
const cartnToGrid = Mat4.invert(Mat4(), gridToCartn);
|
||||
const [nx, ny, nz] = volume.grid.cells.space.dimensions;
|
||||
|
||||
const a = nx - 1;
|
||||
const b = ny - 1;
|
||||
const c = nz - 1;
|
||||
const a = nx;
|
||||
const b = ny;
|
||||
const c = nz;
|
||||
|
||||
const dirA = Vec3.create(a, 0, 0);
|
||||
const dirB = Vec3.create(0, b, 0);
|
||||
@@ -287,9 +287,9 @@ async function createPlaneImage(ctx: VisualContext, volume: Volume, key: number,
|
||||
const cartnToGrid = Mat4.invert(Mat4(), gridToCartn);
|
||||
const [mx, my, mz] = volume.grid.cells.space.dimensions;
|
||||
|
||||
const a = mx - 1;
|
||||
const b = my - 1;
|
||||
const c = mz - 1;
|
||||
const a = mx;
|
||||
const b = my;
|
||||
const c = mz;
|
||||
|
||||
const resolution = Math.max(a, b, c) / Math.max(mx, my, mz);
|
||||
const scaleFactor = 1 / resolution;
|
||||
@@ -399,32 +399,32 @@ function getSliceInfo(grid: Grid, props: SliceProps) {
|
||||
let [nx, ny, nz] = space.dimensions;
|
||||
|
||||
if (dim === 'x') {
|
||||
x = index, y = ny - 1, z = nz - 1;
|
||||
x = index, y = ny, z = nz;
|
||||
width = nz, height = ny;
|
||||
x0 = x, nx = x0 + 1;
|
||||
} else if (dim === 'y') {
|
||||
x = nx - 1, y = index, z = nz - 1;
|
||||
x = nx, y = index, z = nz;
|
||||
width = nz, height = nx;
|
||||
y0 = y, ny = y0 + 1;
|
||||
} else if (dim === 'z') {
|
||||
x = nx - 1, y = ny - 1, z = index;
|
||||
x = nx, y = ny, z = index;
|
||||
width = nx, height = ny;
|
||||
z0 = z, nz = z0 + 1;
|
||||
} else if (dim === 'relativeX') {
|
||||
x = getRelativeIndex(nx, index);
|
||||
y = ny - 1;
|
||||
z = nz - 1;
|
||||
y = ny;
|
||||
z = nz;
|
||||
width = nz, height = ny;
|
||||
x0 = x, nx = x0 + 1;
|
||||
} else if (dim === 'relativeY') {
|
||||
x = nx - 1;
|
||||
x = nx;
|
||||
y = getRelativeIndex(ny, index);
|
||||
z = nz - 1;
|
||||
z = nz;
|
||||
width = nz, height = nx;
|
||||
y0 = y, ny = y0 + 1;
|
||||
} else /* if (dim === 'relativeZ') */ {
|
||||
x = nx - 1;
|
||||
y = ny - 1;
|
||||
x = nx;
|
||||
y = ny;
|
||||
z = getRelativeIndex(nz, index);
|
||||
width = nx, height = ny;
|
||||
z0 = z, nz = z0 + 1;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Grid, Volume } from '../../mol-model/volume';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { Interval, OrderedSet, SortedArray } from '../../mol-data/int';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
@@ -15,6 +15,7 @@ import { Box3D } from '../../mol-math/geometry';
|
||||
import { toHalfFloat } from '../../mol-util/number-conversion';
|
||||
import { clamp } from '../../mol-math/interpolate';
|
||||
import { LocationIterator } from '../../mol-geo/util/location-iterator';
|
||||
import { Tensor } from '../../mol-math/linear-algebra/tensor';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3set = Vec3.set;
|
||||
@@ -351,3 +352,48 @@ export function createSegmentTexture2d(volume: Volume, set: number[], bbox: Box3
|
||||
|
||||
return textureImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new volume that is wrapped by one cell in all dimensions.
|
||||
* Reuses the original volume grid data with new data accessors.
|
||||
* Only intended for isosurface construction.
|
||||
*/
|
||||
export function createWrappedVolume(volume: Volume): Volume {
|
||||
const { grid } = volume;
|
||||
const { space } = grid.cells;
|
||||
const { get, set, add, dataOffset } = space;
|
||||
const [xn, yn, zn] = space.dimensions as Vec3;
|
||||
|
||||
const _dimensions = Vec3.create(xn + 1, yn + 1, zn + 1);
|
||||
|
||||
const _get = (data: Tensor.Data, x: number, y: number, z: number) => get(data, x % xn, y % yn, z % zn);
|
||||
const _set = (data: Tensor.Data, x: number, y: number, z: number, d: number) => set(data, x % xn, y % yn, z % zn, d);
|
||||
const _add = (data: Tensor.Data, x: number, y: number, z: number, d: number) => add(data, x % xn, y % yn, z % zn, d);
|
||||
const _dataOffset = (x: number, y: number, z: number) => dataOffset(x % xn, y % yn, z % zn);
|
||||
|
||||
const _space: Tensor.Space = {
|
||||
...space,
|
||||
dimensions: _dimensions,
|
||||
get: _get,
|
||||
set: _set,
|
||||
add: _add,
|
||||
dataOffset: _dataOffset,
|
||||
};
|
||||
|
||||
const matrix = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const _transform: Grid.Transform = { kind: 'matrix', matrix };
|
||||
|
||||
const _grid: Grid = {
|
||||
...grid,
|
||||
transform: _transform,
|
||||
cells: {
|
||||
...grid.cells,
|
||||
space: _space
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...volume,
|
||||
grid: _grid
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function ExternalVolumeColorTheme(ctx: ThemeDataContext, props: PD.Values
|
||||
}
|
||||
|
||||
const value = getTrilinearlyInterpolated(position);
|
||||
if (isNaN(value)) return defaultColor;
|
||||
if (Number.isNaN(value)) return defaultColor;
|
||||
|
||||
if (usePalette) {
|
||||
return (clamp((value - domain[0]) / (domain[1] - domain[0]), 0, 1) * ColorTheme.PaletteScale) as Color;
|
||||
|
||||
@@ -87,7 +87,7 @@ export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<Vo
|
||||
}
|
||||
|
||||
const value = getTrilinearlyInterpolated(location.position);
|
||||
if (isNaN(value)) return props.defaultColor;
|
||||
if (Number.isNaN(value)) return props.defaultColor;
|
||||
|
||||
return (clamp((value - domain[0]) / (domain[1] - domain[0]), 0, 1) * ColorTheme.PaletteScale) as Color;
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ export function getArrayDigitCount(xs: ArrayLike<number>, maxDigits: number, del
|
||||
export function isInteger(s: string) {
|
||||
s = s.trim();
|
||||
const n = parseInt(s, 10);
|
||||
return isNaN(n) ? false : n.toString() === s;
|
||||
return Number.isNaN(n) ? false : n.toString() === s;
|
||||
}
|
||||
|
||||
export function getPrecision(v: number) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -85,7 +85,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, false, true, Vec3.create(0, 1, 2), true);
|
||||
const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridDataDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2), true);
|
||||
webgl.waitForGpuCommandsCompleteSync();
|
||||
console.timeEnd('gpu mc vert');
|
||||
console.timeEnd('gpu mc');
|
||||
|
||||
Reference in New Issue
Block a user