1. 1 : import { AABB } from './AABB.js';
  2. 2 : import { Vector3 } from './Vector3.js';
  3. 3 :
  4. 4 : const v1 = new Vector3();
  5. 5 : const v2 = new Vector3();
  6. 6 : const v3 = new Vector3();
  7. 7 :
  8. 8 : const xAxis = new Vector3( 1, 0, 0 );
  9. 9 : const yAxis = new Vector3( 0, 1, 0 );
  10. 10 : const zAxis = new Vector3( 0, 0, 1 );
  11. 11 :
  12. 12 : const triangle = { a: new Vector3(), b: new Vector3(), c: new Vector3() };
  13. 13 : const intersection = new Vector3();
  14. 14 : const intersections = new Array();
  15. 15 :
  16. 16 : /**
  17. 17 : * Class representing a bounding volume hierarchy. The current implementation
  18. 18 : * represents single BVH nodes as AABBs. It accepts arbitrary branching factors
  19. 19 : * and can subdivide the given geometry until a defined hierarchy depth has been reached.
  20. 20 : * Besides, the hierarchy construction is performed top-down and the algorithm only
  21. 21 : * performs splits along the cardinal axes.
  22. 22 : *
  23. 23 : * Reference: Bounding Volume Hierarchies in Real-Time Collision Detection
  24. 24 : * by Christer Ericson (chapter 6).
  25. 25 : *
  26. 26 : * @author {@link https://github.com/robp94|robp94}
  27. 27 : * @author {@link https://github.com/Mugen87|Mugen87}
  28. 28 : */
  29. 29 : class BVH {
  30. 30 :
  31. 31 : /**
  32. 32 : * Constructs a new BVH.
  33. 33 : *
  34. 34 : * @param {Number} branchingFactor - The branching factor.
  35. 35 : * @param {Number} primitivesPerNode - The minimum amount of primitives per BVH node.
  36. 36 : * @param {Number} depth - The maximum hierarchical depth.
  37. 37 : */
  38. 38 : constructor( branchingFactor = 2, primitivesPerNode = 1, depth = 10 ) {
  39. 39 :
  40. 40 : /**
  41. 41 : * The branching factor (how many nodes per level).
  42. 42 : * @type {Number}
  43. 43 : * @default 2
  44. 44 : */
  45. 45 : this.branchingFactor = branchingFactor;
  46. 46 :
  47. 47 : /**
  48. 48 : * The minimum amount of primitives per BVH node.
  49. 49 : * @type {Number}
  50. 50 : * @default 10
  51. 51 : */
  52. 52 : this.primitivesPerNode = primitivesPerNode;
  53. 53 :
  54. 54 : /**
  55. 55 : * The maximum hierarchical depth.
  56. 56 : * @type {Number}
  57. 57 : * @default 10
  58. 58 : */
  59. 59 : this.depth = depth;
  60. 60 :
  61. 61 : /**
  62. 62 : * The root BVH node.
  63. 63 : * @type {BVHNode}
  64. 64 : * @default null
  65. 65 : */
  66. 66 : this.root = null;
  67. 67 :
  68. 68 : }
  69. 69 :
  70. 70 : /**
  71. 71 : * Computes a BVH for the given mesh geometry.
  72. 72 : *
  73. 73 : * @param {MeshGeometry} geometry - The mesh geometry.
  74. 74 : * @return {BVH} A reference to this BVH.
  75. 75 : */
  76. 76 : fromMeshGeometry( geometry ) {
  77. 77 :
  78. 78 : this.root = new BVHNode();
  79. 79 :
  80. 80 : // primitives
  81. 81 :
  82. 82 : if ( geometry.indices !== null ) geometry = geometry.toTriangleSoup();
  83. 83 :
  84. 84 : const vertices = geometry.vertices;
  85. 85 :
  86. 86 : for ( let i = 0, l = vertices.length; i < l; i ++ ) {
  87. 87 :
  88. 88 : this.root.primitives.push( vertices[ i ] );
  89. 89 :
  90. 90 : }
  91. 91 :
  92. 92 : // centroids
  93. 93 :
  94. 94 : const primitives = this.root.primitives;
  95. 95 :
  96. 96 : for ( let i = 0, l = primitives.length; i < l; i += 9 ) {
  97. 97 :
  98. 98 : v1.fromArray( primitives, i );
  99. 99 : v2.fromArray( primitives, i + 3 );
  100. 100 : v3.fromArray( primitives, i + 6 );
  101. 101 :
  102. 102 : v1.add( v2 ).add( v3 ).divideScalar( 3 );
  103. 103 :
  104. 104 : this.root.centroids.push( v1.x, v1.y, v1.z );
  105. 105 :
  106. 106 : }
  107. 107 :
  108. 108 : // build
  109. 109 :
  110. 110 : this.root.build( this.branchingFactor, this.primitivesPerNode, this.depth, 1 );
  111. 111 :
  112. 112 : return this;
  113. 113 :
  114. 114 : }
  115. 115 :
  116. 116 : /**
  117. 117 : * Executes the given callback for each node of the BVH.
  118. 118 : *
  119. 119 : * @param {Function} callback - The callback to execute.
  120. 120 : * @return {BVH} A reference to this BVH.
  121. 121 : */
  122. 122 : traverse( callback ) {
  123. 123 :
  124. 124 : this.root.traverse( callback );
  125. 125 :
  126. 126 : return this;
  127. 127 :
  128. 128 : }
  129. 129 :
  130. 130 : }
  131. 131 :
  132. 132 : /**
  133. 133 : * A single node in a bounding volume hierarchy (BVH).
  134. 134 : *
  135. 135 : * @author {@link https://github.com/robp94|robp94}
  136. 136 : * @author {@link https://github.com/Mugen87|Mugen87}
  137. 137 : */
  138. 138 : class BVHNode {
  139. 139 :
  140. 140 : /**
  141. 141 : * Constructs a BVH node.
  142. 142 : */
  143. 143 : constructor() {
  144. 144 :
  145. 145 : /**
  146. 146 : * The parent BVH node.
  147. 147 : * @type {BVHNode}
  148. 148 : * @default null
  149. 149 : */
  150. 150 : this.parent = null;
  151. 151 :
  152. 152 : /**
  153. 153 : * The child BVH nodes.
  154. 154 : * @type {Array<BVHNode>}
  155. 155 : */
  156. 156 : this.children = new Array();
  157. 157 :
  158. 158 : /**
  159. 159 : * The bounding volume of this BVH node.
  160. 160 : * @type {AABB}
  161. 161 : */
  162. 162 : this.boundingVolume = new AABB();
  163. 163 :
  164. 164 : /**
  165. 165 : * The primitives (triangles) of this BVH node.
  166. 166 : * Only filled for leaf nodes.
  167. 167 : * @type {Array<Number>}
  168. 168 : */
  169. 169 : this.primitives = new Array();
  170. 170 :
  171. 171 : /**
  172. 172 : * The centroids of the node's triangles.
  173. 173 : * Only filled for leaf nodes.
  174. 174 : * @type {Array<Number>}
  175. 175 : */
  176. 176 : this.centroids = new Array();
  177. 177 :
  178. 178 : }
  179. 179 :
  180. 180 : /**
  181. 181 : * Returns true if this BVH node is a root node.
  182. 182 : *
  183. 183 : * @return {Boolean} Whether this BVH node is a root node or not.
  184. 184 : */
  185. 185 : root() {
  186. 186 :
  187. 187 : return this.parent === null;
  188. 188 :
  189. 189 : }
  190. 190 :
  191. 191 : /**
  192. 192 : * Returns true if this BVH node is a leaf node.
  193. 193 : *
  194. 194 : * @return {Boolean} Whether this BVH node is a leaf node or not.
  195. 195 : */
  196. 196 : leaf() {
  197. 197 :
  198. 198 : return this.children.length === 0;
  199. 199 :
  200. 200 : }
  201. 201 :
  202. 202 : /**
  203. 203 : * Returns the depth of this BVH node in its hierarchy.
  204. 204 : *
  205. 205 : * @return {Number} The hierarchical depth of this BVH node.
  206. 206 : */
  207. 207 : getDepth() {
  208. 208 :
  209. 209 : let depth = 0;
  210. 210 :
  211. 211 : let parent = this.parent;
  212. 212 :
  213. 213 : while ( parent !== null ) {
  214. 214 :
  215. 215 : parent = parent.parent;
  216. 216 : depth ++;
  217. 217 :
  218. 218 : }
  219. 219 :
  220. 220 : return depth;
  221. 221 :
  222. 222 : }
  223. 223 :
  224. 224 : /**
  225. 225 : * Executes the given callback for this BVH node and its ancestors.
  226. 226 : *
  227. 227 : * @param {Function} callback - The callback to execute.
  228. 228 : * @return {BVHNode} A reference to this BVH node.
  229. 229 : */
  230. 230 : traverse( callback ) {
  231. 231 :
  232. 232 : callback( this );
  233. 233 :
  234. 234 : for ( let i = 0, l = this.children.length; i < l; i ++ ) {
  235. 235 :
  236. 236 : this.children[ i ].traverse( callback );
  237. 237 :
  238. 238 : }
  239. 239 :
  240. 240 : return this;
  241. 241 :
  242. 242 : }
  243. 243 :
  244. 244 : /**
  245. 245 : * Builds this BVH node. That means the respective bounding volume
  246. 246 : * is computed and the node's primitives are distributed under new child nodes.
  247. 247 : * This only happens if the maximum hierarchical depth is not yet reached and
  248. 248 : * the node does contain enough primitives required for a split.
  249. 249 : *
  250. 250 : * @param {Number} branchingFactor - The branching factor.
  251. 251 : * @param {Number} primitivesPerNode - The minimum amount of primitives per BVH node.
  252. 252 : * @param {Number} maxDepth - The maximum hierarchical depth.
  253. 253 : * @param {Number} currentDepth - The current hierarchical depth.
  254. 254 : * @return {BVHNode} A reference to this BVH node.
  255. 255 : */
  256. 256 : build( branchingFactor, primitivesPerNode, maxDepth, currentDepth ) {
  257. 257 :
  258. 258 : this.computeBoundingVolume();
  259. 259 :
  260. 260 : // check depth and primitive count
  261. 261 :
  262. 262 : const primitiveCount = this.primitives.length / 9;
  263. 263 : const newPrimitiveCount = Math.floor( primitiveCount / branchingFactor );
  264. 264 :
  265. 265 : if ( ( currentDepth <= maxDepth ) && ( newPrimitiveCount >= primitivesPerNode ) ) {
  266. 266 :
  267. 267 : // split (distribute primitives on new child BVH nodes)
  268. 268 :
  269. 269 : this.split( branchingFactor );
  270. 270 :
  271. 271 : // proceed with build on the next hierarchy level
  272. 272 :
  273. 273 : for ( let i = 0; i < branchingFactor; i ++ ) {
  274. 274 :
  275. 275 : this.children[ i ].build( branchingFactor, primitivesPerNode, maxDepth, currentDepth + 1 );
  276. 276 :
  277. 277 : }
  278. 278 :
  279. 279 : }
  280. 280 :
  281. 281 : return this;
  282. 282 :
  283. 283 : }
  284. 284 :
  285. 285 : /**
  286. 286 : * Computes the AABB for this BVH node.
  287. 287 : *
  288. 288 : * @return {BVHNode} A reference to this BVH node.
  289. 289 : */
  290. 290 : computeBoundingVolume() {
  291. 291 :
  292. 292 : const primitives = this.primitives;
  293. 293 :
  294. 294 : const aabb = this.boundingVolume;
  295. 295 :
  296. 296 : // compute AABB
  297. 297 :
  298. 298 : aabb.min.set( Infinity, Infinity, Infinity );
  299. 299 : aabb.max.set( - Infinity, - Infinity, - Infinity );
  300. 300 :
  301. 301 : for ( let i = 0, l = primitives.length; i < l; i += 3 ) {
  302. 302 :
  303. 303 : v1.x = primitives[ i ];
  304. 304 : v1.y = primitives[ i + 1 ];
  305. 305 : v1.z = primitives[ i + 2 ];
  306. 306 :
  307. 307 : aabb.expand( v1 );
  308. 308 :
  309. 309 : }
  310. 310 :
  311. 311 : return this;
  312. 312 :
  313. 313 : }
  314. 314 :
  315. 315 : /**
  316. 316 : * Computes the split axis. Right now, only the cardinal axes
  317. 317 : * are potential split axes.
  318. 318 : *
  319. 319 : * @return {Vector3} The split axis.
  320. 320 : */
  321. 321 : computeSplitAxis() {
  322. 322 :
  323. 323 : let maxX, maxY, maxZ = maxY = maxX = - Infinity;
  324. 324 : let minX, minY, minZ = minY = minX = Infinity;
  325. 325 :
  326. 326 : const centroids = this.centroids;
  327. 327 :
  328. 328 : for ( let i = 0, l = centroids.length; i < l; i += 3 ) {
  329. 329 :
  330. 330 : const x = centroids[ i ];
  331. 331 : const y = centroids[ i + 1 ];
  332. 332 : const z = centroids[ i + 2 ];
  333. 333 :
  334. 334 : if ( x > maxX ) {
  335. 335 :
  336. 336 : maxX = x;
  337. 337 :
  338. 338 : }
  339. 339 :
  340. 340 : if ( y > maxY ) {
  341. 341 :
  342. 342 : maxY = y;
  343. 343 :
  344. 344 : }
  345. 345 :
  346. 346 : if ( z > maxZ ) {
  347. 347 :
  348. 348 : maxZ = z;
  349. 349 :
  350. 350 : }
  351. 351 :
  352. 352 : if ( x < minX ) {
  353. 353 :
  354. 354 : minX = x;
  355. 355 :
  356. 356 : }
  357. 357 :
  358. 358 : if ( y < minY ) {
  359. 359 :
  360. 360 : minY = y;
  361. 361 :
  362. 362 : }
  363. 363 :
  364. 364 : if ( z < minZ ) {
  365. 365 :
  366. 366 : minZ = z;
  367. 367 :
  368. 368 : }
  369. 369 :
  370. 370 : }
  371. 371 :
  372. 372 : const rangeX = maxX - minX;
  373. 373 : const rangeY = maxY - minY;
  374. 374 : const rangeZ = maxZ - minZ;
  375. 375 :
  376. 376 : if ( rangeX > rangeY && rangeX > rangeZ ) {
  377. 377 :
  378. 378 : return xAxis;
  379. 379 :
  380. 380 : } else if ( rangeY > rangeZ ) {
  381. 381 :
  382. 382 : return yAxis;
  383. 383 :
  384. 384 : } else {
  385. 385 :
  386. 386 : return zAxis;
  387. 387 :
  388. 388 : }
  389. 389 :
  390. 390 : }
  391. 391 :
  392. 392 : /**
  393. 393 : * Splits the node and distributes node's primitives over new child nodes.
  394. 394 : *
  395. 395 : * @param {Number} branchingFactor - The branching factor.
  396. 396 : * @return {BVHNode} A reference to this BVH node.
  397. 397 : */
  398. 398 : split( branchingFactor ) {
  399. 399 :
  400. 400 : const centroids = this.centroids;
  401. 401 : const primitives = this.primitives;
  402. 402 :
  403. 403 : // create (empty) child BVH nodes
  404. 404 :
  405. 405 : for ( let i = 0; i < branchingFactor; i ++ ) {
  406. 406 :
  407. 407 : this.children[ i ] = new BVHNode();
  408. 408 : this.children[ i ].parent = this;
  409. 409 :
  410. 410 : }
  411. 411 :
  412. 412 : // sort primitives along split axis
  413. 413 :
  414. 414 : const axis = this.computeSplitAxis();
  415. 415 : const sortedPrimitiveIndices = new Array();
  416. 416 :
  417. 417 : for ( let i = 0, l = centroids.length; i < l; i += 3 ) {
  418. 418 :
  419. 419 : v1.fromArray( centroids, i );
  420. 420 :
  421. 421 : // the result from the dot product is our sort criterion.
  422. 422 : // it represents the projection of the centroid on the split axis
  423. 423 :
  424. 424 : const p = v1.dot( axis );
  425. 425 : const primitiveIndex = i / 3;
  426. 426 :
  427. 427 : sortedPrimitiveIndices.push( { index: primitiveIndex, p: p } );
  428. 428 :
  429. 429 : }
  430. 430 :
  431. 431 : sortedPrimitiveIndices.sort( sortPrimitives );
  432. 432 :
  433. 433 : // distribute data
  434. 434 :
  435. 435 : const primitveCount = sortedPrimitiveIndices.length;
  436. 436 : const primitivesPerChild = Math.floor( primitveCount / branchingFactor );
  437. 437 :
  438. 438 : var childIndex = 0;
  439. 439 : var primitivesIndex = 0;
  440. 440 :
  441. 441 : for ( let i = 0; i < primitveCount; i ++ ) {
  442. 442 :
  443. 443 : // selected child
  444. 444 :
  445. 445 : primitivesIndex ++;
  446. 446 :
  447. 447 : // check if we try to add more primitives to a child than "primitivesPerChild" defines.
  448. 448 : // move primitives to the next child
  449. 449 :
  450. 450 : if ( primitivesIndex > primitivesPerChild ) {
  451. 451 :
  452. 452 : // ensure "childIndex" does not overflow (meaning the last child takes all remaining primitives)
  453. 453 :
  454. 454 : if ( childIndex < ( branchingFactor - 1 ) ) {
  455. 455 :
  456. 456 : primitivesIndex = 1; // reset primitive index
  457. 457 : childIndex ++; // raise child index
  458. 458 :
  459. 459 : }
  460. 460 :
  461. 461 : }
  462. 462 :
  463. 463 : const child = this.children[ childIndex ];
  464. 464 :
  465. 465 : // move data to the next level
  466. 466 :
  467. 467 : // 1. primitives
  468. 468 :
  469. 469 : const primitiveIndex = sortedPrimitiveIndices[ i ].index;
  470. 470 : const stride = primitiveIndex * 9; // remember the "primitives" array holds raw vertex data defining triangles
  471. 471 :
  472. 472 : v1.fromArray( primitives, stride );
  473. 473 : v2.fromArray( primitives, stride + 3 );
  474. 474 : v3.fromArray( primitives, stride + 6 );
  475. 475 :
  476. 476 : child.primitives.push( v1.x, v1.y, v1.z );
  477. 477 : child.primitives.push( v2.x, v2.y, v2.z );
  478. 478 : child.primitives.push( v3.x, v3.y, v3.z );
  479. 479 :
  480. 480 : // 2. centroid
  481. 481 :
  482. 482 : v1.fromArray( centroids, primitiveIndex * 3 );
  483. 483 :
  484. 484 : child.centroids.push( v1.x, v1.y, v1.z );
  485. 485 :
  486. 486 : }
  487. 487 :
  488. 488 : // remove centroids/primitives after split from this node
  489. 489 :
  490. 490 : this.centroids.length = 0;
  491. 491 : this.primitives.length = 0;
  492. 492 :
  493. 493 : return this;
  494. 494 :
  495. 495 : }
  496. 496 :
  497. 497 : /**
  498. 498 : * Performs a ray/BVH node intersection test and stores the closest intersection point
  499. 499 : * to the given 3D vector. If no intersection is detected, *null* is returned.
  500. 500 : *
  501. 501 : * @param {Ray} ray - The ray.
  502. 502 : * @param {Vector3} result - The result vector.
  503. 503 : * @return {Vector3} The result vector.
  504. 504 : */
  505. 505 : intersectRay( ray, result ) {
  506. 506 :
  507. 507 : // gather all intersection points along the hierarchy
  508. 508 :
  509. 509 : if ( ray.intersectAABB( this.boundingVolume, result ) !== null ) {
  510. 510 :
  511. 511 : if ( this.leaf() === true ) {
  512. 512 :
  513. 513 : const vertices = this.primitives;
  514. 514 :
  515. 515 : for ( let i = 0, l = vertices.length; i < l; i += 9 ) {
  516. 516 :
  517. 517 : // remember: we assume primitives are triangles
  518. 518 :
  519. 519 : triangle.a.fromArray( vertices, i );
  520. 520 : triangle.b.fromArray( vertices, i + 3 );
  521. 521 : triangle.c.fromArray( vertices, i + 6 );
  522. 522 :
  523. 523 : if ( ray.intersectTriangle( triangle, true, result ) !== null ) {
  524. 524 :
  525. 525 : intersections.push( result.clone() );
  526. 526 :
  527. 527 : }
  528. 528 :
  529. 529 : }
  530. 530 :
  531. 531 : } else {
  532. 532 :
  533. 533 : // process childs
  534. 534 :
  535. 535 : for ( let i = 0, l = this.children.length; i < l; i ++ ) {
  536. 536 :
  537. 537 : this.children[ i ].intersectRay( ray, result );
  538. 538 :
  539. 539 : }
  540. 540 :
  541. 541 : }
  542. 542 :
  543. 543 : }
  544. 544 :
  545. 545 : // determine the closest intersection point in the root node (so after
  546. 546 : // the hierarchy was processed)
  547. 547 :
  548. 548 : if ( this.root() === true ) {
  549. 549 :
  550. 550 : if ( intersections.length > 0 ) {
  551. 551 :
  552. 552 : let minDistance = Infinity;
  553. 553 :
  554. 554 : for ( let i = 0, l = intersections.length; i < l; i ++ ) {
  555. 555 :
  556. 556 : const squaredDistance = ray.origin.squaredDistanceTo( intersections[ i ] );
  557. 557 :
  558. 558 : if ( squaredDistance < minDistance ) {
  559. 559 :
  560. 560 : minDistance = squaredDistance;
  561. 561 : result.copy( intersections[ i ] );
  562. 562 :
  563. 563 : }
  564. 564 :
  565. 565 : }
  566. 566 :
  567. 567 : // reset array
  568. 568 :
  569. 569 : intersections.length = 0;
  570. 570 :
  571. 571 : // return closest intersection point
  572. 572 :
  573. 573 : return result;
  574. 574 :
  575. 575 : } else {
  576. 576 :
  577. 577 : // no intersection detected
  578. 578 :
  579. 579 : return null;
  580. 580 :
  581. 581 : }
  582. 582 :
  583. 583 : } else {
  584. 584 :
  585. 585 : // always return null for non-root nodes
  586. 586 :
  587. 587 : return null;
  588. 588 :
  589. 589 : }
  590. 590 :
  591. 591 : }
  592. 592 :
  593. 593 : /**
  594. 594 : * Performs a ray/BVH node intersection test. Returns either true or false if
  595. 595 : * there is a intersection or not.
  596. 596 : *
  597. 597 : * @param {Ray} ray - The ray.
  598. 598 : * @return {boolean} Whether there is an intersection or not.
  599. 599 : */
  600. 600 : intersectsRay( ray ) {
  601. 601 :
  602. 602 : if ( ray.intersectAABB( this.boundingVolume, intersection ) !== null ) {
  603. 603 :
  604. 604 : if ( this.leaf() === true ) {
  605. 605 :
  606. 606 : const vertices = this.primitives;
  607. 607 :
  608. 608 : for ( let i = 0, l = vertices.length; i < l; i += 9 ) {
  609. 609 :
  610. 610 : // remember: we assume primitives are triangles
  611. 611 :
  612. 612 : triangle.a.fromArray( vertices, i );
  613. 613 : triangle.b.fromArray( vertices, i + 3 );
  614. 614 : triangle.c.fromArray( vertices, i + 6 );
  615. 615 :
  616. 616 : if ( ray.intersectTriangle( triangle, true, intersection ) !== null ) {
  617. 617 :
  618. 618 : return true;
  619. 619 :
  620. 620 : }
  621. 621 :
  622. 622 : }
  623. 623 :
  624. 624 : return false;
  625. 625 :
  626. 626 : } else {
  627. 627 :
  628. 628 : // process child BVH nodes
  629. 629 :
  630. 630 : for ( let i = 0, l = this.children.length; i < l; i ++ ) {
  631. 631 :
  632. 632 : if ( this.children[ i ].intersectsRay( ray ) === true ) {
  633. 633 :
  634. 634 : return true;
  635. 635 :
  636. 636 : }
  637. 637 :
  638. 638 : }
  639. 639 :
  640. 640 : return false;
  641. 641 :
  642. 642 : }
  643. 643 :
  644. 644 : } else {
  645. 645 :
  646. 646 : return false;
  647. 647 :
  648. 648 : }
  649. 649 :
  650. 650 : }
  651. 651 :
  652. 652 : }
  653. 653 :
  654. 654 : //
  655. 655 :
  656. 656 : function sortPrimitives( a, b ) {
  657. 657 :
  658. 658 : return a.p - b.p;
  659. 659 :
  660. 660 : }
  661. 661 :
  662. 662 : export { BVH, BVHNode };