direct-volume.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ValueCell } from '../../../mol-util'
  7. import { Sphere3D, Box3D } from '../../../mol-math/geometry'
  8. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  9. import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
  10. import { Vec3, Mat4, Vec2 } from '../../../mol-math/linear-algebra';
  11. import { Box } from '../../primitive/box';
  12. import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
  13. import { Texture } from '../../../mol-gl/webgl/texture';
  14. import { LocationIterator } from '../../../mol-geo/util/location-iterator';
  15. import { TransformData } from '../transform-data';
  16. import { createColors } from '../color-data';
  17. import { createMarkers } from '../marker-data';
  18. import { GeometryUtils } from '../geometry';
  19. import { transformPositionArray } from '../../../mol-geo/util';
  20. import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
  21. import { Theme } from '../../../mol-theme/theme';
  22. import { RenderableState } from '../../../mol-gl/renderable';
  23. import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
  24. import { Color } from '../../../mol-util/color';
  25. import { BaseGeometry } from '../base';
  26. import { createEmptyOverpaint } from '../overpaint-data';
  27. import { createEmptyTransparency } from '../transparency-data';
  28. import { hashFnv32a } from '../../../mol-data/util';
  29. const VolumeBox = Box()
  30. const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
  31. export interface DirectVolume {
  32. readonly kind: 'direct-volume',
  33. readonly gridTexture: ValueCell<Texture>,
  34. readonly gridTextureDim: ValueCell<Vec3>,
  35. readonly gridDimension: ValueCell<Vec3>,
  36. readonly bboxSize: ValueCell<Vec3>
  37. readonly bboxMin: ValueCell<Vec3>
  38. readonly bboxMax: ValueCell<Vec3>
  39. readonly transform: ValueCell<Mat4>
  40. /** Bounding sphere of the volume */
  41. boundingSphere: Sphere3D
  42. }
  43. export namespace DirectVolume {
  44. export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume {
  45. return directVolume ?
  46. update(bbox, gridDimension, transform, texture, directVolume) :
  47. fromData(bbox, gridDimension, transform, texture)
  48. }
  49. function hashCode(directVolume: DirectVolume) {
  50. return hashFnv32a([
  51. directVolume.bboxSize.ref.version, directVolume.gridDimension.ref.version,
  52. directVolume.gridTexture.ref.version, directVolume.transform.ref.version,
  53. ])
  54. }
  55. function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture): DirectVolume {
  56. const boundingSphere = Sphere3D()
  57. let currentHash = -1
  58. const width = texture.getWidth()
  59. const height = texture.getHeight()
  60. const depth = texture.getDepth()
  61. const directVolume = {
  62. kind: 'direct-volume' as const,
  63. gridDimension: ValueCell.create(gridDimension),
  64. gridTexture: ValueCell.create(texture),
  65. gridTextureDim: ValueCell.create(Vec3.create(width, height, depth)),
  66. bboxMin: ValueCell.create(bbox.min),
  67. bboxMax: ValueCell.create(bbox.max),
  68. bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
  69. transform: ValueCell.create(transform),
  70. get boundingSphere() {
  71. const newHash = hashCode(directVolume)
  72. if (newHash !== currentHash) {
  73. const b = getBoundingSphere(directVolume.gridDimension.ref.value, directVolume.transform.ref.value)
  74. Sphere3D.copy(boundingSphere, b)
  75. currentHash = newHash
  76. }
  77. return boundingSphere
  78. },
  79. }
  80. return directVolume
  81. }
  82. function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume: DirectVolume): DirectVolume {
  83. const width = texture.getWidth()
  84. const height = texture.getHeight()
  85. const depth = texture.getDepth()
  86. ValueCell.update(directVolume.gridDimension, gridDimension)
  87. ValueCell.update(directVolume.gridTexture, texture)
  88. ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth))
  89. ValueCell.update(directVolume.bboxMin, bbox.min)
  90. ValueCell.update(directVolume.bboxMax, bbox.max)
  91. ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
  92. ValueCell.update(directVolume.transform, transform)
  93. return directVolume
  94. }
  95. export function createEmpty(directVolume?: DirectVolume): DirectVolume {
  96. return {} as DirectVolume // TODO
  97. }
  98. export const Params = {
  99. ...BaseGeometry.Params,
  100. isoValueNorm: PD.Numeric(0.22, { min: 0, max: 1, step: 0.01 }, { description: 'Normalized Isolevel Value' }),
  101. renderMode: PD.Select('volume', RenderModeOptions),
  102. controlPoints: PD.LineGraph([
  103. Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
  104. Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
  105. ]),
  106. list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
  107. }
  108. export type Params = typeof Params
  109. export const Utils: GeometryUtils<DirectVolume, Params> = {
  110. Params,
  111. createEmpty,
  112. createValues,
  113. createValuesSimple,
  114. updateValues,
  115. updateBoundingSphere,
  116. createRenderableState,
  117. updateRenderableState
  118. }
  119. function createValues(directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): DirectVolumeValues {
  120. const { gridTexture, gridTextureDim } = directVolume
  121. const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume
  122. const { instanceCount, groupCount } = locationIt
  123. const color = createColors(locationIt, theme.color)
  124. const marker = createMarkers(instanceCount * groupCount)
  125. const overpaint = createEmptyOverpaint()
  126. const transparency = createEmptyTransparency()
  127. const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount }
  128. const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere)
  129. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
  130. const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
  131. const transferTex = createTransferFunctionTexture(controlPoints, props.list)
  132. const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 * 5
  133. return {
  134. ...color,
  135. ...marker,
  136. ...overpaint,
  137. ...transparency,
  138. ...transform,
  139. ...BaseGeometry.createValues(props, counts),
  140. aPosition: ValueCell.create(VolumeBox.vertices as Float32Array),
  141. elements: ValueCell.create(VolumeBox.indices as Uint32Array),
  142. boundingSphere: ValueCell.create(boundingSphere),
  143. invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
  144. uIsoValue: ValueCell.create(props.isoValueNorm),
  145. uBboxMin: bboxMin,
  146. uBboxMax: bboxMax,
  147. uBboxSize: bboxSize,
  148. dMaxSteps: ValueCell.create(maxSteps),
  149. uTransform: gridTransform,
  150. uGridDim: gridDimension,
  151. dRenderMode: ValueCell.create(props.renderMode),
  152. tTransferTex: transferTex,
  153. dGridTexType: ValueCell.create(gridTexture.ref.value.getDepth() > 0 ? '3d' : '2d'),
  154. uGridTexDim: gridTextureDim,
  155. tGridTex: gridTexture,
  156. }
  157. }
  158. function createValuesSimple(directVolume: DirectVolume, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
  159. const s = BaseGeometry.createSimple(colorValue, sizeValue, transform)
  160. const p = { ...PD.getDefaultValues(Params), ...props }
  161. return createValues(directVolume, s.transform, s.locationIterator, s.theme, p)
  162. }
  163. function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
  164. ValueCell.updateIfChanged(values.uIsoValue, props.isoValueNorm)
  165. ValueCell.updateIfChanged(values.uAlpha, props.alpha)
  166. ValueCell.updateIfChanged(values.dRenderMode, props.renderMode)
  167. const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
  168. createTransferFunctionTexture(controlPoints, props.list, values.tTransferTex)
  169. }
  170. function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
  171. const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere)
  172. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
  173. if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
  174. ValueCell.update(values.boundingSphere, boundingSphere)
  175. }
  176. if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) {
  177. ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere)
  178. }
  179. }
  180. function createRenderableState(props: PD.Values<Params>): RenderableState {
  181. const state = BaseGeometry.createRenderableState(props)
  182. state.opaque = false
  183. return state
  184. }
  185. function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
  186. BaseGeometry.updateRenderableState(state, props)
  187. state.opaque = false
  188. }
  189. }
  190. //
  191. const mTmp = Mat4.identity()
  192. const mTmp2 = Mat4.identity()
  193. const vHalfUnit = Vec3.create(0.5, 0.5, 0.5)
  194. const tmpVertices = new Float32Array(VolumeBox.vertices.length)
  195. function getBoundingSphere(gridDimension: Vec3, gridTransform: Mat4) {
  196. tmpVertices.set(VolumeBox.vertices)
  197. Mat4.fromTranslation(mTmp, vHalfUnit)
  198. Mat4.mul(mTmp, Mat4.fromScaling(mTmp2, gridDimension), mTmp)
  199. Mat4.mul(mTmp, gridTransform, mTmp)
  200. transformPositionArray(mTmp, tmpVertices, 0, tmpVertices.length / 3)
  201. return calculateInvariantBoundingSphere(tmpVertices, tmpVertices.length / 3, 1)
  202. // return calculateBoundingSphere(tmpVertices, tmpVertices.length / 3, transform, transformCount)
  203. }