gaussian-density.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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 { Box3D } from '../geometry';
  7. import { Vec3, Mat4, Tensor } from '../linear-algebra';
  8. import { RuntimeContext, Task } from 'mol-task';
  9. import { PositionData, DensityData } from './common';
  10. import { OrderedSet } from 'mol-data/int';
  11. import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object';
  12. import { createContext } from 'mol-gl/webgl/context';
  13. import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density';
  14. import { RenderableState } from 'mol-gl/renderable';
  15. import { ValueCell } from 'mol-util';
  16. import { createRenderTarget } from 'mol-gl/webgl/render-target';
  17. export const DefaultGaussianDensityProps = {
  18. resolution: 1,
  19. radiusOffset: 0,
  20. smoothness: 1.5
  21. }
  22. export type GaussianDensityProps = typeof DefaultGaussianDensityProps
  23. function getDelta(box: Box3D, resolution: number) {
  24. const extent = Vec3.sub(Vec3.zero(), box.max, box.min)
  25. const size = Vec3.zero()
  26. Vec3.ceil(size, Vec3.scale(size, extent, resolution))
  27. const delta = Vec3.div(Vec3.zero(), extent, size)
  28. return delta
  29. }
  30. export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) {
  31. return Task.create('Gaussian Density', async ctx => {
  32. const foo = await GaussianDensityGPU(ctx, position, box, radius, props)
  33. console.log('FOOBAR', foo)
  34. return await GaussianDensity(ctx, position, box, radius, props)
  35. });
  36. }
  37. export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
  38. const foo = await GaussianDensityGPU(ctx, position, box, radius, props)
  39. console.log('FOOBAR', foo)
  40. const { resolution, radiusOffset, smoothness } = props
  41. const { indices, x, y, z } = position
  42. const n = OrderedSet.size(indices)
  43. const v = Vec3.zero()
  44. const p = Vec3.zero()
  45. const pad = (radiusOffset + 3) * 3 // TODO calculate max radius
  46. const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
  47. const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
  48. const min = expandedBox.min
  49. const delta = getDelta(Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad)), resolution)
  50. const dim = Vec3.zero()
  51. Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
  52. const space = Tensor.Space(dim, [0, 1, 2], Float32Array)
  53. const data = space.create()
  54. const field = Tensor.create(space, data)
  55. const idData = space.create()
  56. const idField = Tensor.create(space, idData)
  57. const densData = space.create()
  58. const c = Vec3.zero()
  59. const alpha = smoothness
  60. const _r2 = (radiusOffset + 1.4 * 2)
  61. const _radius2 = Vec3.create(_r2, _r2, _r2)
  62. Vec3.mul(_radius2, _radius2, delta)
  63. const updateChunk = Math.ceil(10000 / (_radius2[0] * _radius2[1] * _radius2[2]))
  64. const beg = Vec3.zero()
  65. const end = Vec3.zero()
  66. const gridPad = 1 / Math.max(...delta)
  67. for (let i = 0; i < n; ++i) {
  68. const j = OrderedSet.getAt(indices, i);
  69. Vec3.set(v, x[j], y[j], z[j])
  70. Vec3.sub(v, v, min)
  71. Vec3.mul(c, v, delta)
  72. const rad = radius(j) + radiusOffset
  73. const rSq = rad * rad
  74. const r2 = radiusOffset + rad * 2 + gridPad
  75. const rad2 = Vec3.create(r2, r2, r2)
  76. Vec3.mul(rad2, rad2, delta)
  77. const r2sq = r2 * r2
  78. const [ begX, begY, begZ ] = Vec3.floor(beg, Vec3.sub(beg, c, rad2))
  79. const [ endX, endY, endZ ] = Vec3.ceil(end, Vec3.add(end, c, rad2))
  80. for (let xi = begX; xi < endX; ++xi) {
  81. for (let yi = begY; yi < endY; ++yi) {
  82. for (let zi = begZ; zi < endZ; ++zi) {
  83. Vec3.set(p, xi, yi, zi)
  84. Vec3.div(p, p, delta)
  85. const distSq = Vec3.squaredDistance(p, v)
  86. if (distSq <= r2sq) {
  87. const dens = Math.exp(-alpha * (distSq / rSq))
  88. space.add(data, xi, yi, zi, dens)
  89. if (dens > space.get(densData, xi, yi, zi)) {
  90. space.set(densData, xi, yi, zi, dens)
  91. space.set(idData, xi, yi, zi, i)
  92. }
  93. }
  94. }
  95. }
  96. }
  97. if (i % updateChunk === 0 && ctx.shouldUpdate) {
  98. await ctx.update({ message: 'filling density grid', current: i, max: n });
  99. }
  100. }
  101. const transform = Mat4.identity()
  102. Mat4.fromScaling(transform, Vec3.inverse(Vec3.zero(), delta))
  103. Mat4.setTranslation(transform, expandedBox.min)
  104. return { field, idField, transform }
  105. }
  106. export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) { // }: Promise<DensityData> {
  107. const { resolution, radiusOffset } = props
  108. const { indices, x, y, z } = position
  109. const n = OrderedSet.size(indices)
  110. const positions = new Float32Array(n * 3)
  111. const radii = new Float32Array(n)
  112. const pad = (radiusOffset + 3) * 3 // TODO calculate max radius
  113. const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
  114. const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
  115. const delta = getDelta(Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad)), resolution)
  116. const dim = Vec3.zero()
  117. Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
  118. for (let i = 0; i < n; ++i) {
  119. const j = OrderedSet.getAt(indices, i);
  120. positions[i * 3] = x[j]
  121. positions[i * 3 + 1] = y[j]
  122. positions[i * 3 + 2] = z[j]
  123. radii[i] = radius(j) + radiusOffset
  124. if (i % 10000 === 0 && ctx.shouldUpdate) {
  125. await ctx.update({ message: 'preparing density data', current: i, max: n });
  126. }
  127. }
  128. //
  129. const values: GaussianDensityValues = {
  130. drawCount: ValueCell.create(n),
  131. instanceCount: ValueCell.create(1),
  132. aRadius: ValueCell.create(radii),
  133. aPosition: ValueCell.create(positions),
  134. uCurrentSlice: ValueCell.create(0),
  135. uCurrentX: ValueCell.create(0),
  136. uCurrentY: ValueCell.create(0),
  137. uBboxMin: ValueCell.create(expandedBox.min),
  138. uBboxMax: ValueCell.create(expandedBox.max),
  139. uBboxSize: ValueCell.create(extent),
  140. uGridDim: ValueCell.create(dim),
  141. }
  142. const state: RenderableState = {
  143. visible: true,
  144. depthMask: false
  145. }
  146. const canvas = document.createElement('canvas')
  147. const gl = canvas.getContext('webgl', {
  148. alpha: false,
  149. antialias: true,
  150. depth: true,
  151. preserveDrawingBuffer: true
  152. })
  153. if (!gl) throw new Error('Could not create a WebGL rendering context')
  154. const webgl = createContext(gl)
  155. const renderObject = createGaussianDensityRenderObject(values, state)
  156. const renderable = createRenderable(webgl, renderObject)
  157. //
  158. // get actual max texture size
  159. const maxTexSize = 4096; // gl. .limits.maxTextureSize;
  160. let fboTexDimX = 0
  161. let fboTexDimY = dim[1]
  162. let fboTexRows = 1
  163. let fboTexCols = dim[0]
  164. if(maxTexSize < dim[0] * dim[2]) {
  165. fboTexCols = Math.floor(maxTexSize / dim[0])
  166. fboTexRows = Math.ceil(dim[2] / fboTexCols)
  167. fboTexDimX = fboTexCols * dim[0]
  168. fboTexDimY *= fboTexRows
  169. } else {
  170. fboTexDimX = dim[0] * dim[2]
  171. }
  172. //
  173. const program = renderable.getProgram('draw')
  174. const renderTarget = createRenderTarget(webgl, fboTexDimX, fboTexDimY)
  175. program.use()
  176. renderTarget.bind()
  177. gl.disable(gl.CULL_FACE)
  178. gl.frontFace(gl.CCW)
  179. gl.cullFace(gl.BACK)
  180. gl.depthMask(true)
  181. gl.clearColor(0, 0, 0, 1)
  182. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  183. gl.depthMask(false)
  184. gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
  185. gl.blendEquation(gl.FUNC_ADD)
  186. gl.enable(gl.BLEND)
  187. gl.finish();
  188. let currCol = 0;
  189. let currY = 0;
  190. let currX = 0;
  191. for(let i = 0; i < dim[2]; ++i) {
  192. if (currCol >= fboTexCols) {
  193. currCol -= fboTexCols
  194. currY += dim[1]
  195. currX = 0
  196. }
  197. gl.viewport(currX, currY, dim[0], dim[1])
  198. ValueCell.update(values.uCurrentSlice, i)
  199. ValueCell.update(values.uCurrentX, currX)
  200. ValueCell.update(values.uCurrentY, currY)
  201. renderable.render('draw')
  202. ++currCol
  203. currX += dim[0]
  204. }
  205. gl.finish();
  206. const imageData = renderTarget.getImageData()
  207. console.log(imageData)
  208. debugTexture(imageData, 0.4)
  209. //
  210. const transform = Mat4.identity()
  211. Mat4.fromScaling(transform, Vec3.inverse(Vec3.zero(), delta))
  212. Mat4.setTranslation(transform, expandedBox.min)
  213. return { field: imageData, idField: undefined, transform }
  214. }
  215. function debugTexture(imageData: ImageData, scale: number) {
  216. const canvas = document.createElement('canvas')
  217. canvas.width = imageData.width
  218. canvas.height = imageData.height
  219. const ctx = canvas.getContext('2d')
  220. if (!ctx) throw new Error('Could not create canvas 2d context')
  221. ctx.putImageData(imageData, 0, 0)
  222. canvas.toBlob(function(imgBlob){
  223. var objectURL = window.URL.createObjectURL(imgBlob)
  224. var img = document.createElement('img')
  225. img.src = objectURL
  226. img.style.width = imageData.width * scale + 'px'
  227. img.style.height = imageData.height * scale + 'px'
  228. img.style.position = 'absolute'
  229. img.style.top = '0px'
  230. img.style.left = '0px'
  231. document.body.appendChild(img)
  232. }, 'image/png')
  233. }