diff --git a/src/mol-gl/compute/marching-cubes/isosurface.ts b/src/mol-gl/compute/marching-cubes/isosurface.ts index 62e3f228dfccb56de2c7d1f47dad804cd30a4c71..81cbafc1ec37c4e2bd61a8ef2161f203b1d548b2 100644 --- a/src/mol-gl/compute/marching-cubes/isosurface.ts +++ b/src/mol-gl/compute/marching-cubes/isosurface.ts @@ -32,6 +32,7 @@ const IsosurfaceSchema = { uSize: UniformSpec('f'), uLevels: UniformSpec('f'), uCount: UniformSpec('f'), + uInvert: UniformSpec('b'), uGridDim: UniformSpec('v3'), uGridTexDim: UniformSpec('v3'), @@ -44,7 +45,7 @@ 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, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> { +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): ComputeRenderable<IsosurfaceValues> { if (ctx.namedComputeRenderables[IsosurfaceName]) { const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues; @@ -56,6 +57,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels)); ValueCell.updateIfChanged(v.uLevels, levels); ValueCell.updateIfChanged(v.uCount, count); + ValueCell.updateIfChanged(v.uInvert, invert); ValueCell.update(v.uGridDim, gridDim); ValueCell.update(v.uGridTexDim, gridTexDim); @@ -66,12 +68,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, packedGroup); + ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, 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, packedGroup: boolean) { +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) { // console.log('uSize', Math.pow(2, levels)) const values: IsosurfaceValues = { ...QuadValues, @@ -85,6 +87,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text uSize: ValueCell.create(Math.pow(2, levels)), uLevels: ValueCell.create(levels), uCount: ValueCell.create(count), + uInvert: ValueCell.create(invert), uGridDim: ValueCell.create(gridDim), uGridTexDim: ValueCell.create(gridTexDim), @@ -112,7 +115,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, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { +export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { const { gl, resources, extensions } = ctx; const { pyramidTex, height, levels, scale, count } = histogramPyramid; const width = pyramidTex.getWidth(); @@ -167,7 +170,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, packedGroup); + const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup); ctx.state.currentRenderItemId = -1; const { drawBuffers } = ctx.extensions; @@ -201,7 +204,7 @@ 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, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { +export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { // console.time('calcActiveVoxels'); const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale); // ctx.waitForGpuCommandsCompleteSync(); @@ -213,7 +216,7 @@ export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDi // console.timeEnd('createHistogramPyramid'); // console.time('createIsosurfaceBuffers'); - const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture); + const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, vertexTexture, groupTexture, normalTexture); // ctx.waitForGpuCommandsCompleteSync(); // console.timeEnd('createIsosurfaceBuffers'); diff --git a/src/mol-gl/shader/marching-cubes/isosurface.frag.ts b/src/mol-gl/shader/marching-cubes/isosurface.frag.ts index 619da139e56f282ad58b0fb706493fb2ed91b495..0f5e373ab4b323ff239d85e3bedde7765001500b 100644 --- a/src/mol-gl/shader/marching-cubes/isosurface.frag.ts +++ b/src/mol-gl/shader/marching-cubes/isosurface.frag.ts @@ -18,6 +18,7 @@ uniform float uIsoValue; uniform float uLevels; uniform float uSize; uniform float uCount; +uniform bool uInvert; uniform vec3 uGridDim; uniform vec3 uGridTexDim; @@ -163,6 +164,13 @@ void main(void) { // current vertex for the up to 15 MC cases int currentVertex = vI - idot4(m, starts); + // ensure winding-order is the same for negative and positive iso-levels + if (uInvert) { + int v = imod(currentVertex + 1, 3); + if (v == 1) currentVertex += 2; + else if (v == 0) currentVertex -= 2; + } + // get index into triIndices table int mcIndex = 16 * int(edgeIndex) + currentVertex; vec4 mcData = texture2D(tTriIndices, vec2(imod(mcIndex, 64), mcIndex / 64) / 64.); @@ -273,11 +281,18 @@ void main(void) { voxelPadded(b1 - c3).a - voxelPadded(b1 + c3).a, voxelPadded(b1 - c4).a - voxelPadded(b1 + c4).a )); - mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform))); - gl_FragData[2].xyz = normalMatrix * -vec3( + gl_FragData[2].xyz = -vec3( n0.x + t * (n0.x - n1.x), n0.y + t * (n0.y - n1.y), n0.z + t * (n0.z - n1.z) ); + + // ensure normal-direction is the same for negative and positive iso-levels + if (uInvert) { + gl_FragData[2].xyz *= -1.0; + } + + // apply normal matrix + gl_FragData[2].xyz *= transpose3(inverse3(mat3(uGridTransform))); } `; \ No newline at end of file diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts index f6a56b7890e23fe83d72bd4b5c6eb460cf50be40..fbe020b2f7ed4b4775f51bc9039d2e5d51458a0c 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts +++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts @@ -182,7 +182,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor; const buffer = textureMesh?.doubleBuffer.get(); - const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, buffer?.vertex, buffer?.group, buffer?.normal); + const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, buffer?.vertex, buffer?.group, buffer?.normal); const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure)); const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh); @@ -241,7 +241,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor; const buffer = textureMesh?.doubleBuffer.get(); - const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, buffer?.vertex, buffer?.group, buffer?.normal); + const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, buffer?.vertex, buffer?.group, buffer?.normal); const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure)); const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh); diff --git a/src/mol-repr/volume/isosurface.ts b/src/mol-repr/volume/isosurface.ts index d25e781d9c4ff76ae733c328b2729f0f66a8e13e..adb996d77f23972db7638dd61c97744bd6080ca6 100644 --- a/src/mol-repr/volume/isosurface.ts +++ b/src/mol-repr/volume/isosurface.ts @@ -176,7 +176,7 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl); const buffer = textureMesh?.doubleBuffer.get(); - const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, buffer?.vertex, buffer?.group, buffer?.normal); + const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, buffer?.vertex, buffer?.group, buffer?.normal); const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh); diff --git a/src/tests/browser/marching-cubes.ts b/src/tests/browser/marching-cubes.ts index be0cb574e20b83a5e8a2756cb72d093d323d5fef..1b8713e7858a5bb5e2984c7fcb2bc31c7f00ba7c 100644 --- a/src/tests/browser/marching-cubes.ts +++ b/src/tests/browser/marching-cubes.ts @@ -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, true); + createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, 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, true); + const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true); webgl.waitForGpuCommandsCompleteSync(); console.timeEnd('gpu mc vert'); console.timeEnd('gpu mc');