TypedArrayUtils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. console.warn( "THREE.TypedArrayUtils: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." );
  2. THREE.TypedArrayUtils = {};
  3. /**
  4. * In-place quicksort for typed arrays (e.g. for Float32Array)
  5. * provides fast sorting
  6. * useful e.g. for a custom shader and/or BufferGeometry
  7. *
  8. * Complexity: http://bigocheatsheet.com/ see Quicksort
  9. *
  10. * Example:
  11. * points: [x, y, z, x, y, z, x, y, z, ...]
  12. * eleSize: 3 //because of (x, y, z)
  13. * orderElement: 0 //order according to x
  14. */
  15. THREE.TypedArrayUtils.quicksortIP = function ( arr, eleSize, orderElement ) {
  16. var stack = [];
  17. var sp = - 1;
  18. var left = 0;
  19. var right = arr.length / eleSize - 1;
  20. var tmp = 0.0, x = 0, y = 0;
  21. var swapF = function ( a, b ) {
  22. a *= eleSize; b *= eleSize;
  23. for ( y = 0; y < eleSize; y ++ ) {
  24. tmp = arr[ a + y ];
  25. arr[ a + y ] = arr[ b + y ];
  26. arr[ b + y ] = tmp;
  27. }
  28. };
  29. var i, j, swap = new Float32Array( eleSize ), temp = new Float32Array( eleSize );
  30. while ( true ) {
  31. if ( right - left <= 25 ) {
  32. for ( j = left + 1; j <= right; j ++ ) {
  33. for ( x = 0; x < eleSize; x ++ ) {
  34. swap[ x ] = arr[ j * eleSize + x ];
  35. }
  36. i = j - 1;
  37. while ( i >= left && arr[ i * eleSize + orderElement ] > swap[ orderElement ] ) {
  38. for ( x = 0; x < eleSize; x ++ ) {
  39. arr[ ( i + 1 ) * eleSize + x ] = arr[ i * eleSize + x ];
  40. }
  41. i --;
  42. }
  43. for ( x = 0; x < eleSize; x ++ ) {
  44. arr[ ( i + 1 ) * eleSize + x ] = swap[ x ];
  45. }
  46. }
  47. if ( sp == - 1 ) break;
  48. right = stack[ sp -- ]; //?
  49. left = stack[ sp -- ];
  50. } else {
  51. var median = ( left + right ) >> 1;
  52. i = left + 1;
  53. j = right;
  54. swapF( median, i );
  55. if ( arr[ left * eleSize + orderElement ] > arr[ right * eleSize + orderElement ] ) {
  56. swapF( left, right );
  57. }
  58. if ( arr[ i * eleSize + orderElement ] > arr[ right * eleSize + orderElement ] ) {
  59. swapF( i, right );
  60. }
  61. if ( arr[ left * eleSize + orderElement ] > arr[ i * eleSize + orderElement ] ) {
  62. swapF( left, i );
  63. }
  64. for ( x = 0; x < eleSize; x ++ ) {
  65. temp[ x ] = arr[ i * eleSize + x ];
  66. }
  67. while ( true ) {
  68. do i ++; while ( arr[ i * eleSize + orderElement ] < temp[ orderElement ] );
  69. do j --; while ( arr[ j * eleSize + orderElement ] > temp[ orderElement ] );
  70. if ( j < i ) break;
  71. swapF( i, j );
  72. }
  73. for ( x = 0; x < eleSize; x ++ ) {
  74. arr[ ( left + 1 ) * eleSize + x ] = arr[ j * eleSize + x ];
  75. arr[ j * eleSize + x ] = temp[ x ];
  76. }
  77. if ( right - i + 1 >= j - left ) {
  78. stack[ ++ sp ] = i;
  79. stack[ ++ sp ] = right;
  80. right = j - 1;
  81. } else {
  82. stack[ ++ sp ] = left;
  83. stack[ ++ sp ] = j - 1;
  84. left = i;
  85. }
  86. }
  87. }
  88. return arr;
  89. };
  90. /**
  91. * k-d Tree for typed arrays (e.g. for Float32Array), in-place
  92. * provides fast nearest neighbour search
  93. * useful e.g. for a custom shader and/or BufferGeometry, saves tons of memory
  94. * has no insert and remove, only buildup and neares neighbour search
  95. *
  96. * Based on https://github.com/ubilabs/kd-tree-javascript by Ubilabs
  97. *
  98. * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
  99. *
  100. * Requires typed array quicksort
  101. *
  102. * Example:
  103. * points: [x, y, z, x, y, z, x, y, z, ...]
  104. * metric: function(a, b){ return Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2); } //Manhatten distance
  105. * eleSize: 3 //because of (x, y, z)
  106. *
  107. * Further information (including mathematical properties)
  108. * http://en.wikipedia.org/wiki/Binary_tree
  109. * http://en.wikipedia.org/wiki/K-d_tree
  110. *
  111. * If you want to further minimize memory usage, remove Node.depth and replace in search algorithm with a traversal to root node (see comments at THREE.TypedArrayUtils.Kdtree.prototype.Node)
  112. */
  113. THREE.TypedArrayUtils.Kdtree = function ( points, metric, eleSize ) {
  114. var scope = this;
  115. var maxDepth = 0;
  116. var getPointSet = function ( points, pos ) {
  117. return points.subarray( pos * eleSize, pos * eleSize + eleSize );
  118. };
  119. function buildTree( points, depth, parent, pos ) {
  120. var dim = depth % eleSize,
  121. median,
  122. node,
  123. plength = points.length / eleSize;
  124. if ( depth > maxDepth ) maxDepth = depth;
  125. if ( plength === 0 ) return null;
  126. if ( plength === 1 ) {
  127. return new scope.Node( getPointSet( points, 0 ), depth, parent, pos );
  128. }
  129. THREE.TypedArrayUtils.quicksortIP( points, eleSize, dim );
  130. median = Math.floor( plength / 2 );
  131. node = new scope.Node( getPointSet( points, median ), depth, parent, median + pos );
  132. node.left = buildTree( points.subarray( 0, median * eleSize ), depth + 1, node, pos );
  133. node.right = buildTree( points.subarray( ( median + 1 ) * eleSize, points.length ), depth + 1, node, pos + median + 1 );
  134. return node;
  135. }
  136. this.root = buildTree( points, 0, null, 0 );
  137. this.getMaxDepth = function () {
  138. return maxDepth;
  139. };
  140. this.nearest = function ( point, maxNodes, maxDistance ) {
  141. /* point: array of size eleSize
  142. maxNodes: max amount of nodes to return
  143. maxDistance: maximum distance to point result nodes should have
  144. condition (not implemented): function to test node before it's added to the result list, e.g. test for view frustum
  145. */
  146. var i,
  147. result,
  148. bestNodes;
  149. bestNodes = new THREE.TypedArrayUtils.Kdtree.BinaryHeap(
  150. function ( e ) {
  151. return - e[ 1 ];
  152. }
  153. );
  154. function nearestSearch( node ) {
  155. var bestChild,
  156. dimension = node.depth % eleSize,
  157. ownDistance = metric( point, node.obj ),
  158. linearDistance = 0,
  159. otherChild,
  160. i,
  161. linearPoint = [];
  162. function saveNode( node, distance ) {
  163. bestNodes.push( [ node, distance ] );
  164. if ( bestNodes.size() > maxNodes ) {
  165. bestNodes.pop();
  166. }
  167. }
  168. for ( i = 0; i < eleSize; i += 1 ) {
  169. if ( i === node.depth % eleSize ) {
  170. linearPoint[ i ] = point[ i ];
  171. } else {
  172. linearPoint[ i ] = node.obj[ i ];
  173. }
  174. }
  175. linearDistance = metric( linearPoint, node.obj );
  176. // if it's a leaf
  177. if ( node.right === null && node.left === null ) {
  178. if ( bestNodes.size() < maxNodes || ownDistance < bestNodes.peek()[ 1 ] ) {
  179. saveNode( node, ownDistance );
  180. }
  181. return;
  182. }
  183. if ( node.right === null ) {
  184. bestChild = node.left;
  185. } else if ( node.left === null ) {
  186. bestChild = node.right;
  187. } else {
  188. if ( point[ dimension ] < node.obj[ dimension ] ) {
  189. bestChild = node.left;
  190. } else {
  191. bestChild = node.right;
  192. }
  193. }
  194. // recursive search
  195. nearestSearch( bestChild );
  196. if ( bestNodes.size() < maxNodes || ownDistance < bestNodes.peek()[ 1 ] ) {
  197. saveNode( node, ownDistance );
  198. }
  199. // if there's still room or the current distance is nearer than the best distance
  200. if ( bestNodes.size() < maxNodes || Math.abs( linearDistance ) < bestNodes.peek()[ 1 ] ) {
  201. if ( bestChild === node.left ) {
  202. otherChild = node.right;
  203. } else {
  204. otherChild = node.left;
  205. }
  206. if ( otherChild !== null ) {
  207. nearestSearch( otherChild );
  208. }
  209. }
  210. }
  211. if ( maxDistance ) {
  212. for ( i = 0; i < maxNodes; i += 1 ) {
  213. bestNodes.push( [ null, maxDistance ] );
  214. }
  215. }
  216. nearestSearch( scope.root );
  217. result = [];
  218. for ( i = 0; i < maxNodes; i += 1 ) {
  219. if ( bestNodes.content[ i ][ 0 ] ) {
  220. result.push( [ bestNodes.content[ i ][ 0 ], bestNodes.content[ i ][ 1 ] ] );
  221. }
  222. }
  223. return result;
  224. };
  225. };
  226. /**
  227. * If you need to free up additional memory and agree with an additional O( log n ) traversal time you can get rid of "depth" and "pos" in Node:
  228. * Depth can be easily done by adding 1 for every parent (care: root node has depth 0, not 1)
  229. * Pos is a bit tricky: Assuming the tree is balanced (which is the case when after we built it up), perform the following steps:
  230. * By traversing to the root store the path e.g. in a bit pattern (01001011, 0 is left, 1 is right)
  231. * From buildTree we know that "median = Math.floor( plength / 2 );", therefore for each bit...
  232. * 0: amountOfNodesRelevantForUs = Math.floor( (pamountOfNodesRelevantForUs - 1) / 2 );
  233. * 1: amountOfNodesRelevantForUs = Math.ceil( (pamountOfNodesRelevantForUs - 1) / 2 );
  234. * pos += Math.floor( (pamountOfNodesRelevantForUs - 1) / 2 );
  235. * when recursion done, we still need to add all left children of target node:
  236. * pos += Math.floor( (pamountOfNodesRelevantForUs - 1) / 2 );
  237. * and I think you need to +1 for the current position, not sure.. depends, try it out ^^
  238. *
  239. * I experienced that for 200'000 nodes you can get rid of 4 MB memory each, leading to 8 MB memory saved.
  240. */
  241. THREE.TypedArrayUtils.Kdtree.prototype.Node = function ( obj, depth, parent, pos ) {
  242. this.obj = obj;
  243. this.left = null;
  244. this.right = null;
  245. this.parent = parent;
  246. this.depth = depth;
  247. this.pos = pos;
  248. };
  249. /**
  250. * Binary heap implementation
  251. */
  252. THREE.TypedArrayUtils.Kdtree.BinaryHeap = function ( scoreFunction ) {
  253. this.content = [];
  254. this.scoreFunction = scoreFunction;
  255. };
  256. THREE.TypedArrayUtils.Kdtree.BinaryHeap.prototype = {
  257. push: function ( element ) {
  258. // Add the new element to the end of the array.
  259. this.content.push( element );
  260. // Allow it to bubble up.
  261. this.bubbleUp( this.content.length - 1 );
  262. },
  263. pop: function () {
  264. // Store the first element so we can return it later.
  265. var result = this.content[ 0 ];
  266. // Get the element at the end of the array.
  267. var end = this.content.pop();
  268. // If there are any elements left, put the end element at the
  269. // start, and let it sink down.
  270. if ( this.content.length > 0 ) {
  271. this.content[ 0 ] = end;
  272. this.sinkDown( 0 );
  273. }
  274. return result;
  275. },
  276. peek: function () {
  277. return this.content[ 0 ];
  278. },
  279. remove: function ( node ) {
  280. var len = this.content.length;
  281. // To remove a value, we must search through the array to find it.
  282. for ( var i = 0; i < len; i ++ ) {
  283. if ( this.content[ i ] == node ) {
  284. // When it is found, the process seen in 'pop' is repeated
  285. // to fill up the hole.
  286. var end = this.content.pop();
  287. if ( i != len - 1 ) {
  288. this.content[ i ] = end;
  289. if ( this.scoreFunction( end ) < this.scoreFunction( node ) ) {
  290. this.bubbleUp( i );
  291. } else {
  292. this.sinkDown( i );
  293. }
  294. }
  295. return;
  296. }
  297. }
  298. throw new Error( "Node not found." );
  299. },
  300. size: function () {
  301. return this.content.length;
  302. },
  303. bubbleUp: function ( n ) {
  304. // Fetch the element that has to be moved.
  305. var element = this.content[ n ];
  306. // When at 0, an element can not go up any further.
  307. while ( n > 0 ) {
  308. // Compute the parent element's index, and fetch it.
  309. var parentN = Math.floor( ( n + 1 ) / 2 ) - 1,
  310. parent = this.content[ parentN ];
  311. // Swap the elements if the parent is greater.
  312. if ( this.scoreFunction( element ) < this.scoreFunction( parent ) ) {
  313. this.content[ parentN ] = element;
  314. this.content[ n ] = parent;
  315. // Update 'n' to continue at the new position.
  316. n = parentN;
  317. } else {
  318. // Found a parent that is less, no need to move it further.
  319. break;
  320. }
  321. }
  322. },
  323. sinkDown: function ( n ) {
  324. // Look up the target element and its score.
  325. var length = this.content.length,
  326. element = this.content[ n ],
  327. elemScore = this.scoreFunction( element );
  328. while ( true ) {
  329. // Compute the indices of the child elements.
  330. var child2N = ( n + 1 ) * 2, child1N = child2N - 1;
  331. // This is used to store the new position of the element, if any.
  332. var swap = null;
  333. // If the first child exists (is inside the array)...
  334. if ( child1N < length ) {
  335. // Look it up and compute its score.
  336. var child1 = this.content[ child1N ],
  337. child1Score = this.scoreFunction( child1 );
  338. // If the score is less than our element's, we need to swap.
  339. if ( child1Score < elemScore ) swap = child1N;
  340. }
  341. // Do the same checks for the other child.
  342. if ( child2N < length ) {
  343. var child2 = this.content[ child2N ],
  344. child2Score = this.scoreFunction( child2 );
  345. if ( child2Score < ( swap === null ? elemScore : child1Score ) ) swap = child2N;
  346. }
  347. // If the element needs to be moved, swap it, and continue.
  348. if ( swap !== null ) {
  349. this.content[ n ] = this.content[ swap ];
  350. this.content[ swap ] = element;
  351. n = swap;
  352. } else {
  353. // Otherwise, we are done.
  354. break;
  355. }
  356. }
  357. }
  358. };