direct-volume.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  7. import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
  8. import { Box3D } from '../../mol-math/geometry';
  9. import { Grid, Volume } from '../../mol-model/volume';
  10. import { RuntimeContext } from '../../mol-task';
  11. import { WebGLContext } from '../../mol-gl/webgl/context';
  12. import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
  13. import { VisualContext } from '../visual';
  14. import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
  15. import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
  16. import { LocationIterator } from '../../mol-geo/util/location-iterator';
  17. import { NullLocation } from '../../mol-model/location';
  18. import { VisualUpdateState } from '../util';
  19. import { RepresentationContext, RepresentationParamsGetter } from '../representation';
  20. import { Interval } from '../../mol-data/int';
  21. import { Loci, EmptyLoci } from '../../mol-model/loci';
  22. import { PickingId } from '../../mol-geo/geometry/picking';
  23. import { createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
  24. function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
  25. const bbox = Box3D();
  26. Box3D.add(bbox, gridDimension);
  27. Box3D.transform(bbox, bbox, transform);
  28. return bbox;
  29. }
  30. // 2d volume texture
  31. export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
  32. const gridDimension = volume.grid.cells.space.dimensions as Vec3;
  33. const { width, height } = getVolumeTexture2dLayout(gridDimension);
  34. if(Math.max(width, height) > webgl.maxTextureSize / 2) {
  35. throw new Error('volume too large for direct-volume rendering');
  36. }
  37. const textureImage = createVolumeTexture2d(volume, 'normals');
  38. // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
  39. const transform = Grid.getGridToCartesianTransform(volume.grid);
  40. const bbox = getBoundingBox(gridDimension, transform);
  41. const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
  42. texture.load(textureImage);
  43. const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
  44. return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume);
  45. }
  46. // 3d volume texture
  47. function getUnitToCartn(grid: Grid) {
  48. if (grid.transform.kind === 'matrix') {
  49. return {
  50. unitToCartn: Mat4.mul(Mat4(),
  51. grid.transform.matrix,
  52. Mat4.fromScaling(Mat4(), grid.cells.space.dimensions as Vec3)
  53. ),
  54. cellDim: Mat4.getScaling(Vec3(), grid.transform.matrix)
  55. };
  56. }
  57. const box = grid.transform.fractionalBox;
  58. const size = Box3D.size(Vec3(), box);
  59. return {
  60. unitToCartn: Mat4.mul3(Mat4(),
  61. grid.transform.cell.fromFractional,
  62. Mat4.fromTranslation(Mat4(), box.min),
  63. Mat4.fromScaling(Mat4(), size)
  64. ),
  65. cellDim: Vec3.div(Vec3(), grid.transform.cell.size, grid.cells.space.dimensions as Vec3)
  66. };
  67. }
  68. export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
  69. const gridDimension = volume.grid.cells.space.dimensions as Vec3;
  70. if(Math.max(...gridDimension) > webgl.max3dTextureSize / 2) {
  71. throw new Error('volume too large for direct-volume rendering');
  72. }
  73. const textureVolume = createVolumeTexture3d(volume);
  74. const transform = Grid.getGridToCartesianTransform(volume.grid);
  75. const bbox = getBoundingBox(gridDimension, transform);
  76. const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
  77. texture.load(textureVolume);
  78. const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
  79. return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume);
  80. }
  81. //
  82. export async function createDirectVolume(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
  83. const { runtime, webgl } = ctx;
  84. if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props');
  85. return webgl.isWebGL2 ?
  86. createDirectVolume3d(runtime, webgl, volume, directVolume) :
  87. createDirectVolume2d(runtime, webgl, volume, directVolume);
  88. }
  89. function getLoci(volume: Volume, props: PD.Values<DirectVolumeParams>) {
  90. return props.renderMode.name === 'isosurface'
  91. ? Volume.Isosurface.Loci(volume, props.renderMode.params.isoValue)
  92. : Volume.Loci(volume);
  93. }
  94. export function getDirectVolumeLoci(pickingId: PickingId, volume: Volume, props: DirectVolumeProps, id: number) {
  95. const { objectId, groupId } = pickingId;
  96. if (id === objectId) {
  97. return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
  98. }
  99. return EmptyLoci;
  100. }
  101. export function eachDirectVolume(loci: Loci, volume: Volume, props: DirectVolumeProps, apply: (interval: Interval) => boolean) {
  102. const isoValue = props.renderMode.name === 'isosurface'
  103. ? props.renderMode.params.isoValue : undefined;
  104. return eachVolumeLoci(loci, volume, isoValue, apply);
  105. }
  106. //
  107. export const DirectVolumeParams = {
  108. ...DirectVolume.Params,
  109. quality: { ...DirectVolume.Params.quality, isEssential: false },
  110. };
  111. export type DirectVolumeParams = typeof DirectVolumeParams
  112. export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
  113. const p = PD.clone(DirectVolumeParams);
  114. p.renderMode = DirectVolume.createRenderModeParam(volume.grid.stats);
  115. return p;
  116. }
  117. export type DirectVolumeProps = PD.Values<DirectVolumeParams>
  118. export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolumeParams> {
  119. return VolumeVisual<DirectVolume, DirectVolumeParams>({
  120. defaultProps: PD.getDefaultValues(DirectVolumeParams),
  121. createGeometry: createDirectVolume,
  122. createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
  123. getLoci: getDirectVolumeLoci,
  124. eachLocation: eachDirectVolume,
  125. setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
  126. },
  127. geometryUtils: DirectVolume.Utils,
  128. dispose: (geometry: DirectVolume) => {
  129. geometry.gridTexture.ref.value.destroy();
  130. }
  131. }, materialId);
  132. }
  133. export function DirectVolumeRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, DirectVolumeParams>): VolumeRepresentation<DirectVolumeParams> {
  134. return VolumeRepresentation('Direct Volume', ctx, getParams, DirectVolumeVisual, getLoci);
  135. }
  136. export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
  137. name: 'direct-volume',
  138. label: 'Direct Volume',
  139. description: 'Direct rendering of volumetric data.',
  140. factory: DirectVolumeRepresentation,
  141. getParams: getDirectVolumeParams,
  142. defaultValues: PD.getDefaultValues(DirectVolumeParams),
  143. defaultColorTheme: { name: 'uniform' },
  144. defaultSizeTheme: { name: 'uniform' },
  145. isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
  146. });