PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/examples/jsm/utils/TypedArrayUtils.js

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