grid.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /**
  2. * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. * @author Gianluca Tomasello <giagitom@gmail.com>
  7. */
  8. import { Result, Lookup3D } from './common';
  9. import { Box3D } from '../primitives/box3d';
  10. import { Sphere3D } from '../primitives/sphere3d';
  11. import { PositionData } from '../common';
  12. import { Vec3 } from '../../linear-algebra';
  13. import { OrderedSet } from '../../../mol-data/int';
  14. import { Boundary } from '../boundary';
  15. import { FibonacciHeap } from '../../../mol-util/fibonacci-heap';
  16. interface GridLookup3D<T = number> extends Lookup3D<T> {
  17. readonly buckets: { readonly offset: ArrayLike<number>, readonly count: ArrayLike<number>, readonly array: ArrayLike<number> }
  18. }
  19. function GridLookup3D<T extends number = number>(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number): GridLookup3D<T> {
  20. return new GridLookup3DImpl<T>(data, boundary, cellSizeOrCount);
  21. }
  22. export { GridLookup3D };
  23. class GridLookup3DImpl<T extends number = number> implements GridLookup3D<T> {
  24. private ctx: QueryContext;
  25. boundary: Lookup3D['boundary'];
  26. buckets: GridLookup3D['buckets'];
  27. result: Result<T>;
  28. find(x: number, y: number, z: number, radius: number, result?: Result<T>): Result<T> {
  29. this.ctx.x = x;
  30. this.ctx.y = y;
  31. this.ctx.z = z;
  32. this.ctx.radius = radius;
  33. this.ctx.isCheck = false;
  34. const ret = result ?? this.result;
  35. query(this.ctx, ret);
  36. return ret;
  37. }
  38. nearest(x: number, y: number, z: number, k: number = 1, stopIf?: Function, result?: Result<T>): Result<T> {
  39. this.ctx.x = x;
  40. this.ctx.y = y;
  41. this.ctx.z = z;
  42. this.ctx.k = k;
  43. this.ctx.stopIf = stopIf;
  44. const ret = result ?? this.result;
  45. queryNearest(this.ctx, ret);
  46. return ret;
  47. }
  48. check(x: number, y: number, z: number, radius: number): boolean {
  49. this.ctx.x = x;
  50. this.ctx.y = y;
  51. this.ctx.z = z;
  52. this.ctx.radius = radius;
  53. this.ctx.isCheck = true;
  54. return query(this.ctx, this.result);
  55. }
  56. constructor(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number) {
  57. const structure = build(data, boundary, cellSizeOrCount);
  58. this.ctx = createContext(structure);
  59. this.boundary = { box: structure.boundingBox, sphere: structure.boundingSphere };
  60. this.buckets = { offset: structure.bucketOffset, count: structure.bucketCounts, array: structure.bucketArray };
  61. this.result = Result.create();
  62. }
  63. }
  64. /**
  65. * Adapted from https://github.com/arose/ngl
  66. * MIT License Copyright (C) 2014+ Alexander Rose
  67. */
  68. interface InputData {
  69. x: ArrayLike<number>,
  70. y: ArrayLike<number>,
  71. z: ArrayLike<number>,
  72. indices: OrderedSet,
  73. radius?: ArrayLike<number>,
  74. }
  75. interface Grid3D {
  76. size: number[],
  77. min: number[],
  78. grid: Uint32Array,
  79. delta: number[],
  80. bucketOffset: Uint32Array,
  81. bucketCounts: Int32Array,
  82. bucketArray: Int32Array,
  83. data: InputData,
  84. maxRadius: number,
  85. expandedBox: Box3D,
  86. boundingBox: Box3D,
  87. boundingSphere: Sphere3D
  88. }
  89. interface BuildState {
  90. size: number[],
  91. delta: number[],
  92. data: InputData,
  93. expandedBox: Box3D,
  94. boundingBox: Box3D,
  95. boundingSphere: Sphere3D,
  96. elementCount: number
  97. }
  98. function _build(state: BuildState): Grid3D {
  99. const { expandedBox, size: [sX, sY, sZ], data: { x: px, y: py, z: pz, radius, indices }, elementCount, delta } = state;
  100. const n = sX * sY * sZ;
  101. const { min: [minX, minY, minZ] } = expandedBox;
  102. let maxRadius = 0;
  103. let bucketCount = 0;
  104. const grid = new Uint32Array(n);
  105. const bucketIndex = new Int32Array(elementCount);
  106. for (let t = 0; t < elementCount; t++) {
  107. const i = OrderedSet.getAt(indices, t);
  108. const x = Math.floor((px[i] - minX) / delta[0]);
  109. const y = Math.floor((py[i] - minY) / delta[1]);
  110. const z = Math.floor((pz[i] - minZ) / delta[2]);
  111. const idx = (((x * sY) + y) * sZ) + z;
  112. if ((grid[idx] += 1) === 1) {
  113. bucketCount += 1;
  114. }
  115. bucketIndex[t] = idx;
  116. }
  117. if (radius) {
  118. for (let t = 0; t < elementCount; t++) {
  119. const i = OrderedSet.getAt(indices, t);
  120. if (radius[i] > maxRadius) maxRadius = radius[i];
  121. }
  122. }
  123. const bucketCounts = new Int32Array(bucketCount);
  124. for (let i = 0, j = 0; i < n; i++) {
  125. const c = grid[i];
  126. if (c > 0) {
  127. grid[i] = j + 1;
  128. bucketCounts[j] = c;
  129. j += 1;
  130. }
  131. }
  132. const bucketOffset = new Uint32Array(bucketCount);
  133. for (let i = 1; i < bucketCount; ++i) {
  134. bucketOffset[i] += bucketOffset[i - 1] + bucketCounts[i - 1];
  135. }
  136. const bucketFill = new Int32Array(bucketCount);
  137. const bucketArray = new Int32Array(elementCount);
  138. for (let i = 0; i < elementCount; i++) {
  139. const bucketIdx = grid[bucketIndex[i]];
  140. if (bucketIdx > 0) {
  141. const k = bucketIdx - 1;
  142. bucketArray[bucketOffset[k] + bucketFill[k]] = i;
  143. bucketFill[k] += 1;
  144. }
  145. }
  146. return {
  147. size: state.size,
  148. bucketArray,
  149. bucketCounts,
  150. bucketOffset,
  151. grid,
  152. delta,
  153. min: state.expandedBox.min,
  154. data: state.data,
  155. maxRadius,
  156. expandedBox: state.expandedBox,
  157. boundingBox: state.boundingBox,
  158. boundingSphere: state.boundingSphere
  159. };
  160. }
  161. function build(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number) {
  162. // need to expand the grid bounds to avoid rounding errors
  163. const expandedBox = Box3D.expand(Box3D(), boundary.box, Vec3.create(0.5, 0.5, 0.5));
  164. const { indices } = data;
  165. const S = Box3D.size(Vec3(), expandedBox);
  166. let delta, size;
  167. const elementCount = OrderedSet.size(indices);
  168. const cellCount = typeof cellSizeOrCount === 'number' ? cellSizeOrCount : 32;
  169. const cellSize = Array.isArray(cellSizeOrCount) && cellSizeOrCount;
  170. if (cellSize && !Vec3.isZero(cellSize)) {
  171. size = [Math.ceil(S[0] / cellSize[0]), Math.ceil(S[1] / cellSize[1]), Math.ceil(S[2] / cellSize[2])];
  172. delta = cellSize;
  173. } else if (elementCount > 0) {
  174. // size of the box
  175. // required "grid volume" so that each cell contains on average 'cellCount' elements.
  176. const V = Math.ceil(elementCount / cellCount);
  177. const f = Math.pow(V / (S[0] * S[1] * S[2]), 1 / 3);
  178. size = [Math.ceil(S[0] * f), Math.ceil(S[1] * f), Math.ceil(S[2] * f)];
  179. delta = [S[0] / size[0], S[1] / size[1], S[2] / size[2]];
  180. } else {
  181. delta = S;
  182. size = [1, 1, 1];
  183. }
  184. const inputData: InputData = {
  185. x: data.x,
  186. y: data.y,
  187. z: data.z,
  188. indices,
  189. radius: data.radius
  190. };
  191. const state: BuildState = {
  192. size,
  193. data: inputData,
  194. expandedBox,
  195. boundingBox: boundary.box,
  196. boundingSphere: boundary.sphere,
  197. elementCount,
  198. delta
  199. };
  200. return _build(state);
  201. }
  202. interface QueryContext {
  203. grid: Grid3D,
  204. x: number,
  205. y: number,
  206. z: number,
  207. k: number,
  208. stopIf?: Function,
  209. radius: number,
  210. isCheck: boolean
  211. }
  212. function createContext(grid: Grid3D): QueryContext {
  213. return { grid, x: 0.1, y: 0.1, z: 0.1, k: 1, stopIf: undefined, radius: 0.1, isCheck: false };
  214. }
  215. function query<T extends number = number>(ctx: QueryContext, result: Result<T>): boolean {
  216. const { min, size: [sX, sY, sZ], bucketOffset, bucketCounts, bucketArray, grid, data: { x: px, y: py, z: pz, indices, radius }, delta, maxRadius } = ctx.grid;
  217. const { radius: inputRadius, isCheck, x, y, z } = ctx;
  218. const r = inputRadius + maxRadius;
  219. const rSq = r * r;
  220. Result.reset(result);
  221. const loX = Math.max(0, Math.floor((x - r - min[0]) / delta[0]));
  222. const loY = Math.max(0, Math.floor((y - r - min[1]) / delta[1]));
  223. const loZ = Math.max(0, Math.floor((z - r - min[2]) / delta[2]));
  224. const hiX = Math.min(sX - 1, Math.floor((x + r - min[0]) / delta[0]));
  225. const hiY = Math.min(sY - 1, Math.floor((y + r - min[1]) / delta[1]));
  226. const hiZ = Math.min(sZ - 1, Math.floor((z + r - min[2]) / delta[2]));
  227. if (loX > hiX || loY > hiY || loZ > hiZ) return false;
  228. for (let ix = loX; ix <= hiX; ix++) {
  229. for (let iy = loY; iy <= hiY; iy++) {
  230. for (let iz = loZ; iz <= hiZ; iz++) {
  231. const bucketIdx = grid[(((ix * sY) + iy) * sZ) + iz];
  232. if (bucketIdx === 0) continue;
  233. const k = bucketIdx - 1;
  234. const offset = bucketOffset[k];
  235. const count = bucketCounts[k];
  236. const end = offset + count;
  237. for (let i = offset; i < end; i++) {
  238. const idx = OrderedSet.getAt(indices, bucketArray[i]);
  239. const dx = px[idx] - x;
  240. const dy = py[idx] - y;
  241. const dz = pz[idx] - z;
  242. const distSq = dx * dx + dy * dy + dz * dz;
  243. if (distSq <= rSq) {
  244. if (maxRadius > 0 && Math.sqrt(distSq) - radius![idx] > inputRadius) continue;
  245. if (isCheck) return true;
  246. Result.add(result, bucketArray[i], distSq);
  247. }
  248. }
  249. }
  250. }
  251. }
  252. return result.count > 0;
  253. }
  254. const tmpDirVec = Vec3();
  255. const tmpVec = Vec3();
  256. const tmpSetG = new Set<number>();
  257. const tmpSetG2 = new Set<number>();
  258. const tmpArrG1 = [0.1];
  259. const tmpArrG2 = [0.1];
  260. const tmpArrG3 = [0.1];
  261. const tmpHeapG = new FibonacciHeap();
  262. function queryNearest<T extends number = number>(ctx: QueryContext, result: Result<T>): boolean {
  263. const { min, expandedBox: box, boundingSphere: { center }, size: [sX, sY, sZ], bucketOffset, bucketCounts, bucketArray, grid, data: { x: px, y: py, z: pz, indices, radius }, delta, maxRadius } = ctx.grid;
  264. const { x, y, z, k, stopIf } = ctx;
  265. const indicesCount = OrderedSet.size(indices);
  266. Result.reset(result);
  267. if (indicesCount === 0 || k <= 0) return false;
  268. let gX, gY, gZ, stop = false, gCount = 1, expandGrid = true, nextGCount = 0, arrG = tmpArrG1, nextArrG = tmpArrG2, maxRange = 0, expandRange = true, gridId: number, gridPointsFinished = false;
  269. const expandedArrG = tmpArrG3, sqMaxRadius = maxRadius * maxRadius;
  270. arrG.length = 0;
  271. expandedArrG.length = 0;
  272. tmpSetG.clear();
  273. tmpHeapG.clear();
  274. Vec3.set(tmpVec, x, y, z);
  275. if (!Box3D.containsVec3(box, tmpVec)) {
  276. // intersect ray pointing to box center
  277. Box3D.nearestIntersectionWithRay(tmpVec, box, tmpVec, Vec3.normalize(tmpDirVec, Vec3.sub(tmpDirVec, center, tmpVec)));
  278. gX = Math.max(0, Math.min(sX - 1, Math.floor((tmpVec[0] - min[0]) / delta[0])));
  279. gY = Math.max(0, Math.min(sY - 1, Math.floor((tmpVec[1] - min[1]) / delta[1])));
  280. gZ = Math.max(0, Math.min(sZ - 1, Math.floor((tmpVec[2] - min[2]) / delta[2])));
  281. } else {
  282. gX = Math.floor((x - min[0]) / delta[0]);
  283. gY = Math.floor((y - min[1]) / delta[1]);
  284. gZ = Math.floor((z - min[2]) / delta[2]);
  285. }
  286. const dX = maxRadius !== 0 ? Math.max(1, Math.min(sX - 1, Math.ceil(maxRadius / delta[0]))) : 1;
  287. const dY = maxRadius !== 0 ? Math.max(1, Math.min(sY - 1, Math.ceil(maxRadius / delta[1]))) : 1;
  288. const dZ = maxRadius !== 0 ? Math.max(1, Math.min(sZ - 1, Math.ceil(maxRadius / delta[2]))) : 1;
  289. arrG.push(gX, gY, gZ, (((gX * sY) + gY) * sZ) + gZ);
  290. while (result.count < indicesCount) {
  291. const arrGLen = gCount * 4;
  292. for (let ig = 0; ig < arrGLen; ig += 4) {
  293. gridId = arrG[ig + 3];
  294. if (!tmpSetG.has(gridId)) {
  295. tmpSetG.add(gridId);
  296. gridPointsFinished = tmpSetG.size >= grid.length;
  297. const bucketIdx = grid[gridId];
  298. if (bucketIdx !== 0) {
  299. const _maxRange = maxRange;
  300. const ki = bucketIdx - 1;
  301. const offset = bucketOffset[ki];
  302. const count = bucketCounts[ki];
  303. const end = offset + count;
  304. for (let i = offset; i < end; i++) {
  305. const bIdx = bucketArray[i];
  306. const idx = OrderedSet.getAt(indices, bIdx);
  307. const dx = px[idx] - x;
  308. const dy = py[idx] - y;
  309. const dz = pz[idx] - z;
  310. let distSq = dx * dx + dy * dy + dz * dz;
  311. if (maxRadius !== 0) {
  312. const r = radius![idx];
  313. distSq -= r * r;
  314. }
  315. if (expandRange && distSq > maxRange) {
  316. maxRange = distSq;
  317. }
  318. tmpHeapG.insert(distSq, bIdx);
  319. }
  320. if (_maxRange < maxRange) expandRange = false;
  321. }
  322. }
  323. }
  324. // find next grid points
  325. nextArrG.length = 0;
  326. nextGCount = 0;
  327. tmpSetG2.clear();
  328. for (let ig = 0; ig < arrGLen; ig += 4) {
  329. gX = arrG[ig];
  330. gY = arrG[ig + 1];
  331. gZ = arrG[ig + 2];
  332. // fill grid points array with valid adiacent positions
  333. for (let ix = -dX; ix <= dX; ix++) {
  334. const xPos = gX + ix;
  335. if (xPos < 0 || xPos >= sX) continue;
  336. for (let iy = -dY; iy <= dY; iy++) {
  337. const yPos = gY + iy;
  338. if (yPos < 0 || yPos >= sY) continue;
  339. for (let iz = -dZ; iz <= dZ; iz++) {
  340. const zPos = gZ + iz;
  341. if (zPos < 0 || zPos >= sZ) continue;
  342. gridId = (((xPos * sY) + yPos) * sZ) + zPos;
  343. if (tmpSetG2.has(gridId)) continue; // already scanned
  344. tmpSetG2.add(gridId);
  345. if (tmpSetG.has(gridId)) continue; // already visited
  346. if (!expandGrid) {
  347. const xP = min[0] + xPos * delta[0] - x;
  348. const yP = min[1] + yPos * delta[1] - y;
  349. const zP = min[2] + zPos * delta[2] - z;
  350. const distSqG = (xP * xP) + (yP * yP) + (zP * zP) - sqMaxRadius; // is sqMaxRadius necessary?
  351. if (distSqG > maxRange) {
  352. expandedArrG.push(xPos, yPos, zPos, gridId);
  353. continue;
  354. }
  355. }
  356. nextArrG.push(xPos, yPos, zPos, gridId);
  357. nextGCount++;
  358. }
  359. }
  360. }
  361. }
  362. expandGrid = false;
  363. if (nextGCount === 0) {
  364. if (k === 1) {
  365. const node = tmpHeapG.findMinimum();
  366. if (node) {
  367. const { key: squaredDistance, value: index } = node!;
  368. // const squaredDistance = node!.key, index = node!.value;
  369. Result.add(result, index as number, squaredDistance as number);
  370. return true;
  371. }
  372. } else {
  373. while (!tmpHeapG.isEmpty() && (gridPointsFinished || tmpHeapG.findMinimum()!.key as number <= maxRange) && result.count < k) {
  374. const node = tmpHeapG.extractMinimum();
  375. const squaredDistance = node!.key, index = node!.value;
  376. Result.add(result, index as number, squaredDistance as number);
  377. if (stopIf && !stop) {
  378. stop = stopIf(index, squaredDistance);
  379. }
  380. }
  381. }
  382. if (result.count >= k || stop || result.count >= indicesCount) return result.count > 0;
  383. expandGrid = true;
  384. expandRange = true;
  385. if (expandedArrG.length > 0) {
  386. for (let i = 0, l = expandedArrG.length; i < l; i++) {
  387. arrG.push(expandedArrG[i]);
  388. }
  389. expandedArrG.length = 0;
  390. gCount = arrG.length;
  391. }
  392. } else {
  393. const tmp = arrG;
  394. arrG = nextArrG;
  395. nextArrG = tmp;
  396. gCount = nextGCount;
  397. }
  398. }
  399. return result.count > 0;
  400. }