octreeBlock.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { SmartArrayNoDuplicate } from "../../Misc/smartArray";
  2. import { Vector3 } from "../../Maths/math.vector";
  3. import { Ray } from "../../Culling/ray";
  4. import { BoundingBox } from "../../Culling/boundingBox";
  5. import { Plane } from '../../Maths/math.plane';
  6. /**
  7. * Contains an array of blocks representing the octree
  8. */
  9. export interface IOctreeContainer<T> {
  10. /**
  11. * Blocks within the octree
  12. */
  13. blocks: Array<OctreeBlock<T>>;
  14. }
  15. /**
  16. * Class used to store a cell in an octree
  17. * @see https://doc.babylonjs.com/how_to/optimizing_your_scene_with_octrees
  18. */
  19. export class OctreeBlock<T> {
  20. /**
  21. * Gets the content of the current block
  22. */
  23. public entries = new Array<T>();
  24. /**
  25. * Gets the list of block children
  26. */
  27. public blocks: Array<OctreeBlock<T>>;
  28. private _depth: number;
  29. private _maxDepth: number;
  30. private _capacity: number;
  31. private _minPoint: Vector3;
  32. private _maxPoint: Vector3;
  33. private _boundingVectors = new Array<Vector3>();
  34. private _creationFunc: (entry: T, block: OctreeBlock<T>) => void;
  35. /**
  36. * Creates a new block
  37. * @param minPoint defines the minimum vector (in world space) of the block's bounding box
  38. * @param maxPoint defines the maximum vector (in world space) of the block's bounding box
  39. * @param capacity defines the maximum capacity of this block (if capacity is reached the block will be split into sub blocks)
  40. * @param depth defines the current depth of this block in the octree
  41. * @param maxDepth defines the maximal depth allowed (beyond this value, the capacity is ignored)
  42. * @param creationFunc defines a callback to call when an element is added to the block
  43. */
  44. constructor(minPoint: Vector3, maxPoint: Vector3, capacity: number, depth: number, maxDepth: number, creationFunc: (entry: T, block: OctreeBlock<T>) => void) {
  45. this._capacity = capacity;
  46. this._depth = depth;
  47. this._maxDepth = maxDepth;
  48. this._creationFunc = creationFunc;
  49. this._minPoint = minPoint;
  50. this._maxPoint = maxPoint;
  51. this._boundingVectors.push(minPoint.clone());
  52. this._boundingVectors.push(maxPoint.clone());
  53. this._boundingVectors.push(minPoint.clone());
  54. this._boundingVectors[2].x = maxPoint.x;
  55. this._boundingVectors.push(minPoint.clone());
  56. this._boundingVectors[3].y = maxPoint.y;
  57. this._boundingVectors.push(minPoint.clone());
  58. this._boundingVectors[4].z = maxPoint.z;
  59. this._boundingVectors.push(maxPoint.clone());
  60. this._boundingVectors[5].z = minPoint.z;
  61. this._boundingVectors.push(maxPoint.clone());
  62. this._boundingVectors[6].x = minPoint.x;
  63. this._boundingVectors.push(maxPoint.clone());
  64. this._boundingVectors[7].y = minPoint.y;
  65. }
  66. // Property
  67. /**
  68. * Gets the maximum capacity of this block (if capacity is reached the block will be split into sub blocks)
  69. */
  70. public get capacity(): number {
  71. return this._capacity;
  72. }
  73. /**
  74. * Gets the minimum vector (in world space) of the block's bounding box
  75. */
  76. public get minPoint(): Vector3 {
  77. return this._minPoint;
  78. }
  79. /**
  80. * Gets the maximum vector (in world space) of the block's bounding box
  81. */
  82. public get maxPoint(): Vector3 {
  83. return this._maxPoint;
  84. }
  85. // Methods
  86. /**
  87. * Add a new element to this block
  88. * @param entry defines the element to add
  89. */
  90. public addEntry(entry: T): void {
  91. if (this.blocks) {
  92. for (var index = 0; index < this.blocks.length; index++) {
  93. var block = this.blocks[index];
  94. block.addEntry(entry);
  95. }
  96. return;
  97. }
  98. this._creationFunc(entry, this);
  99. if (this.entries.length > this.capacity && this._depth < this._maxDepth) {
  100. this.createInnerBlocks();
  101. }
  102. }
  103. /**
  104. * Remove an element from this block
  105. * @param entry defines the element to remove
  106. */
  107. public removeEntry(entry: T): void {
  108. if (this.blocks) {
  109. for (var index = 0; index < this.blocks.length; index++) {
  110. var block = this.blocks[index];
  111. block.removeEntry(entry);
  112. }
  113. return;
  114. }
  115. const entryIndex = this.entries.indexOf(entry);
  116. if (entryIndex > -1) {
  117. this.entries.splice(entryIndex, 1);
  118. }
  119. }
  120. /**
  121. * Add an array of elements to this block
  122. * @param entries defines the array of elements to add
  123. */
  124. public addEntries(entries: T[]): void {
  125. for (var index = 0; index < entries.length; index++) {
  126. var mesh = entries[index];
  127. this.addEntry(mesh);
  128. }
  129. }
  130. /**
  131. * Test if the current block intersects the furstum planes and if yes, then add its content to the selection array
  132. * @param frustumPlanes defines the frustum planes to test
  133. * @param selection defines the array to store current content if selection is positive
  134. * @param allowDuplicate defines if the selection array can contains duplicated entries
  135. */
  136. public select(frustumPlanes: Plane[], selection: SmartArrayNoDuplicate<T>, allowDuplicate?: boolean): void {
  137. if (BoundingBox.IsInFrustum(this._boundingVectors, frustumPlanes)) {
  138. if (this.blocks) {
  139. for (var index = 0; index < this.blocks.length; index++) {
  140. var block = this.blocks[index];
  141. block.select(frustumPlanes, selection, allowDuplicate);
  142. }
  143. return;
  144. }
  145. if (allowDuplicate) {
  146. selection.concat(this.entries);
  147. } else {
  148. selection.concatWithNoDuplicate(this.entries);
  149. }
  150. }
  151. }
  152. /**
  153. * Test if the current block intersect with the given bounding sphere and if yes, then add its content to the selection array
  154. * @param sphereCenter defines the bounding sphere center
  155. * @param sphereRadius defines the bounding sphere radius
  156. * @param selection defines the array to store current content if selection is positive
  157. * @param allowDuplicate defines if the selection array can contains duplicated entries
  158. */
  159. public intersects(sphereCenter: Vector3, sphereRadius: number, selection: SmartArrayNoDuplicate<T>, allowDuplicate?: boolean): void {
  160. if (BoundingBox.IntersectsSphere(this._minPoint, this._maxPoint, sphereCenter, sphereRadius)) {
  161. if (this.blocks) {
  162. for (var index = 0; index < this.blocks.length; index++) {
  163. var block = this.blocks[index];
  164. block.intersects(sphereCenter, sphereRadius, selection, allowDuplicate);
  165. }
  166. return;
  167. }
  168. if (allowDuplicate) {
  169. selection.concat(this.entries);
  170. } else {
  171. selection.concatWithNoDuplicate(this.entries);
  172. }
  173. }
  174. }
  175. /**
  176. * Test if the current block intersect with the given ray and if yes, then add its content to the selection array
  177. * @param ray defines the ray to test with
  178. * @param selection defines the array to store current content if selection is positive
  179. */
  180. public intersectsRay(ray: Ray, selection: SmartArrayNoDuplicate<T>): void {
  181. if (ray.intersectsBoxMinMax(this._minPoint, this._maxPoint)) {
  182. if (this.blocks) {
  183. for (var index = 0; index < this.blocks.length; index++) {
  184. var block = this.blocks[index];
  185. block.intersectsRay(ray, selection);
  186. }
  187. return;
  188. }
  189. selection.concatWithNoDuplicate(this.entries);
  190. }
  191. }
  192. /**
  193. * Subdivide the content into child blocks (this block will then be empty)
  194. */
  195. public createInnerBlocks(): void {
  196. OctreeBlock._CreateBlocks(this._minPoint, this._maxPoint, this.entries, this._capacity, this._depth, this._maxDepth, this, this._creationFunc);
  197. }
  198. /**
  199. * @hidden
  200. */
  201. public static _CreateBlocks<T>(worldMin: Vector3, worldMax: Vector3, entries: T[], maxBlockCapacity: number, currentDepth: number, maxDepth: number, target: IOctreeContainer<T>, creationFunc: (entry: T, block: OctreeBlock<T>) => void): void {
  202. target.blocks = new Array<OctreeBlock<T>>();
  203. var blockSize = new Vector3((worldMax.x - worldMin.x) / 2, (worldMax.y - worldMin.y) / 2, (worldMax.z - worldMin.z) / 2);
  204. // Segmenting space
  205. for (var x = 0; x < 2; x++) {
  206. for (var y = 0; y < 2; y++) {
  207. for (var z = 0; z < 2; z++) {
  208. var localMin = worldMin.add(blockSize.multiplyByFloats(x, y, z));
  209. var localMax = worldMin.add(blockSize.multiplyByFloats(x + 1, y + 1, z + 1));
  210. var block = new OctreeBlock<T>(localMin, localMax, maxBlockCapacity, currentDepth + 1, maxDepth, creationFunc);
  211. block.addEntries(entries);
  212. target.blocks.push(block);
  213. }
  214. }
  215. }
  216. }
  217. }