color-smoothing.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /**
  2. * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
  7. import { WebGLContext } from '../../../mol-gl/webgl/context';
  8. import { Texture } from '../../../mol-gl/webgl/texture';
  9. import { Box3D, Sphere3D } from '../../../mol-math/geometry';
  10. import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
  11. import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
  12. import { Color } from '../../../mol-util/color';
  13. interface ColorSmoothingInput {
  14. vertexCount: number
  15. instanceCount: number
  16. groupCount: number
  17. transformBuffer: Float32Array
  18. instanceBuffer: Float32Array
  19. positionBuffer: Float32Array
  20. groupBuffer: Float32Array
  21. colorData: TextureImage<Uint8Array>
  22. colorType: 'group' | 'groupInstance'
  23. boundingSphere: Sphere3D
  24. invariantBoundingSphere: Sphere3D
  25. }
  26. export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl?: WebGLContext, texture?: Texture) {
  27. const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer } = input;
  28. const isInstanceType = colorType.endsWith('Instance');
  29. const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
  30. const scaleFactor = 1 / resolution;
  31. const scaledBox = Box3D.scale(Box3D(), box, scaleFactor);
  32. const gridDim = Box3D.size(Vec3(), scaledBox);
  33. Vec3.ceil(gridDim, gridDim);
  34. Vec3.add(gridDim, gridDim, Vec3.create(2, 2, 2));
  35. const { min } = box;
  36. const [ xn, yn ] = gridDim;
  37. const { width, height } = getVolumeTexture2dLayout(gridDim);
  38. // console.log({ width, height, dim });
  39. const itemSize = 3;
  40. const data = new Float32Array(width * height * itemSize);
  41. const count = new Float32Array(width * height);
  42. const grid = new Uint8Array(width * height * itemSize);
  43. const textureImage: TextureImage<Uint8Array> = { array: grid, width, height, filter: 'linear' };
  44. const instanceCount = isInstanceType ? input.instanceCount : 1;
  45. const colors = input.colorData.array;
  46. function getIndex(x: number, y: number, z: number) {
  47. const column = Math.floor(((z * xn) % width) / xn);
  48. const row = Math.floor((z * xn) / width);
  49. const px = column * xn + x;
  50. return itemSize * ((row * yn * width) + (y * width) + px);
  51. }
  52. const p = 2;
  53. const [dimX, dimY, dimZ] = gridDim;
  54. const v = Vec3();
  55. for (let i = 0; i < instanceCount; ++i) {
  56. for (let j = 0; j < vertexCount; j += stride) {
  57. Vec3.fromArray(v, positionBuffer, j * 3);
  58. if (isInstanceType) Vec3.transformMat4Offset(v, v, transformBuffer, 0, 0, i * 16);
  59. Vec3.sub(v, v, min);
  60. Vec3.scale(v, v, scaleFactor);
  61. const [vx, vy, vz] = v;
  62. // vertex mapped to grid
  63. const x = Math.floor(vx);
  64. const y = Math.floor(vy);
  65. const z = Math.floor(vz);
  66. // group colors
  67. const ci = i * groupCount + groupBuffer[j];
  68. const r = colors[ci * 3];
  69. const g = colors[ci * 3 + 1];
  70. const b = colors[ci * 3 + 2];
  71. // Extents of grid to consider for this atom
  72. const begX = Math.max(0, x - p);
  73. const begY = Math.max(0, y - p);
  74. const begZ = Math.max(0, z - p);
  75. // Add two to these points:
  76. // - x, y, z are floor'd values so this ensures coverage
  77. // - these are loop limits (exclusive)
  78. const endX = Math.min(dimX, x + p + 2);
  79. const endY = Math.min(dimY, y + p + 2);
  80. const endZ = Math.min(dimZ, z + p + 2);
  81. for (let xi = begX; xi < endX; ++xi) {
  82. const dx = xi - vx;
  83. for (let yi = begY; yi < endY; ++yi) {
  84. const dy = yi - vy;
  85. for (let zi = begZ; zi < endZ; ++zi) {
  86. const dz = zi - vz;
  87. const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
  88. if (d > p) continue;
  89. let s = p - d;
  90. const index = getIndex(xi, yi, zi);
  91. data[index] += r * s;
  92. data[index + 1] += g * s;
  93. data[index + 2] += b * s;
  94. count[index / 3] += s;
  95. }
  96. }
  97. }
  98. }
  99. }
  100. for (let i = 0, il = count.length; i < il; ++i) {
  101. const i3 = i * 3;
  102. const c = count[i];
  103. grid[i3] = Math.round(data[i3] / c);
  104. grid[i3 + 1] = Math.round(data[i3 + 1] / c);
  105. grid[i3 + 2] = Math.round(data[i3 + 2] / c);
  106. }
  107. const gridTexDim = Vec2.create(width, height);
  108. const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor);
  109. const type = isInstanceType ? 'volumeInstance' as const : 'volume' as const;
  110. if (webgl) {
  111. if (!texture) texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
  112. texture.load(textureImage);
  113. return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
  114. } else {
  115. const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 });
  116. return {
  117. kind: 'vertex' as const,
  118. texture: interpolated,
  119. texDim: Vec2.create(interpolated.width, interpolated.height),
  120. type: isInstanceType ? 'vertexInstance' : 'vertex'
  121. };
  122. }
  123. }
  124. //
  125. interface ColorInterpolationInput {
  126. vertexCount: number
  127. instanceCount: number
  128. transformBuffer: Float32Array
  129. positionBuffer: Float32Array
  130. colorType: 'volumeInstance' | 'volume'
  131. grid: Uint8Array // 2d layout
  132. gridTexDim: Vec2
  133. gridDim: Vec3
  134. gridTransform: Vec4
  135. vertexStride: number
  136. colorStride: number
  137. }
  138. export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
  139. const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input;
  140. const isInstanceType = input.colorType.endsWith('Instance');
  141. const instanceCount = isInstanceType ? input.instanceCount : 1;
  142. const image = createTextureImage(Math.max(1, instanceCount * vertexCount), 3, Uint8Array);
  143. const { array } = image;
  144. const [xn, yn] = gridDim;
  145. const width = gridTexDim[0];
  146. const min = Vec3.fromArray(Vec3(), gridTransform, 0);
  147. const scaleFactor = gridTransform[3];
  148. function getIndex(x: number, y: number, z: number) {
  149. const column = Math.floor(((z * xn) % width) / xn);
  150. const row = Math.floor((z * xn) / width);
  151. const px = column * xn + x;
  152. return colorStride * ((row * yn * width) + (y * width) + px);
  153. }
  154. const v = Vec3();
  155. const v0 = Vec3();
  156. const v1 = Vec3();
  157. const vd = Vec3();
  158. for (let i = 0; i < instanceCount; ++i) {
  159. for (let j = 0; j < vertexCount; ++j) {
  160. Vec3.fromArray(v, positionBuffer, j * vertexStride);
  161. if (isInstanceType) Vec3.transformMat4Offset(v, v, transformBuffer, 0, 0, i * 16);
  162. Vec3.sub(v, v, min);
  163. Vec3.scale(v, v, scaleFactor);
  164. Vec3.floor(v0, v);
  165. Vec3.ceil(v1, v);
  166. Vec3.sub(vd, v, v0);
  167. Vec3.sub(v, v1, v0);
  168. Vec3.div(vd, vd, v);
  169. const [x0, y0, z0] = v0;
  170. const [x1, y1, z1] = v1;
  171. const [xd, yd, zd] = vd;
  172. const s000 = Color.fromArray(grid, getIndex(x0, y0, z0));
  173. const s100 = Color.fromArray(grid, getIndex(x1, y0, z0));
  174. const s001 = Color.fromArray(grid, getIndex(x0, y0, z1));
  175. const s101 = Color.fromArray(grid, getIndex(x1, y0, z1));
  176. const s010 = Color.fromArray(grid, getIndex(x0, y1, z0));
  177. const s110 = Color.fromArray(grid, getIndex(x1, y1, z0));
  178. const s011 = Color.fromArray(grid, getIndex(x0, y1, z1));
  179. const s111 = Color.fromArray(grid, getIndex(x1, y1, z1));
  180. const s00 = Color.interpolate(s000, s100, xd);
  181. const s01 = Color.interpolate(s001, s101, xd);
  182. const s10 = Color.interpolate(s010, s110, xd);
  183. const s11 = Color.interpolate(s011, s111, xd);
  184. const s0 = Color.interpolate(s00, s10, yd);
  185. const s1 = Color.interpolate(s01, s11, yd);
  186. Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3);
  187. }
  188. }
  189. return image;
  190. }