surfaceNet.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /**
  2. * SurfaceNets in JavaScript
  3. *
  4. * Written by Mikola Lysenko (C) 2012
  5. *
  6. * MIT License
  7. *
  8. * Based on: S.F. Gibson, 'Constrained Elastic Surface Nets'. (1998) MERL Tech Report.
  9. * from https://github.com/mikolalysenko/isosurface/tree/master
  10. *
  11. */
  12. let surfaceNet = ( dims, potential, bounds ) => {
  13. //Precompute edge table, like Paul Bourke does.
  14. // This saves a bit of time when computing the centroid of each boundary cell
  15. var cube_edges = new Int32Array(24) , edge_table = new Int32Array(256);
  16. (function() {
  17. //Initialize the cube_edges table
  18. // This is just the vertex number of each cube
  19. var k = 0;
  20. for(var i=0; i<8; ++i) {
  21. for(var j=1; j<=4; j<<=1) {
  22. var p = i^j;
  23. if(i <= p) {
  24. cube_edges[k++] = i;
  25. cube_edges[k++] = p;
  26. }
  27. }
  28. }
  29. //Initialize the intersection table.
  30. // This is a 2^(cube configuration) -> 2^(edge configuration) map
  31. // There is one entry for each possible cube configuration, and the output is a 12-bit vector enumerating all edges crossing the 0-level.
  32. for(var i=0; i<256; ++i) {
  33. var em = 0;
  34. for(var j=0; j<24; j+=2) {
  35. var a = !!(i & (1<<cube_edges[j]))
  36. , b = !!(i & (1<<cube_edges[j+1]));
  37. em |= a !== b ? (1 << (j >> 1)) : 0;
  38. }
  39. edge_table[i] = em;
  40. }
  41. })();
  42. //Internal buffer, this may get resized at run time
  43. var buffer = new Array(4096);
  44. (function() {
  45. for(var i=0; i<buffer.length; ++i) {
  46. buffer[i] = 0;
  47. }
  48. })();
  49. if(!bounds) {
  50. bounds = [[0,0,0],dims];
  51. }
  52. var scale = [0,0,0];
  53. var shift = [0,0,0];
  54. for(var i=0; i<3; ++i) {
  55. scale[i] = (bounds[1][i] - bounds[0][i]) / dims[i];
  56. shift[i] = bounds[0][i];
  57. }
  58. var vertices = []
  59. , faces = []
  60. , n = 0
  61. , x = [0, 0, 0]
  62. , R = [1, (dims[0]+1), (dims[0]+1)*(dims[1]+1)]
  63. , grid = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  64. , buf_no = 1;
  65. //Resize buffer if necessary
  66. if(R[2] * 2 > buffer.length) {
  67. var ol = buffer.length;
  68. buffer.length = R[2] * 2;
  69. while(ol < buffer.length) {
  70. buffer[ol++] = 0;
  71. }
  72. }
  73. //March over the voxel grid
  74. for(x[2]=0; x[2]<dims[2]-1; ++x[2], n+=dims[0], buf_no ^= 1, R[2]=-R[2]) {
  75. //m is the pointer into the buffer we are going to use.
  76. //This is slightly obtuse because javascript does not have good support for packed data structures, so we must use typed arrays :(
  77. //The contents of the buffer will be the indices of the vertices on the previous x/y slice of the volume
  78. var m = 1 + (dims[0]+1) * (1 + buf_no * (dims[1]+1));
  79. for(x[1]=0; x[1]<dims[1]-1; ++x[1], ++n, m+=2)
  80. for(x[0]=0; x[0]<dims[0]-1; ++x[0], ++n, ++m) {
  81. //Read in 8 field values around this vertex and store them in an array
  82. //Also calculate 8-bit mask, like in marching cubes, so we can speed up sign checks later
  83. var mask = 0, g = 0;
  84. for(var k=0; k<2; ++k)
  85. for(var j=0; j<2; ++j)
  86. for(var i=0; i<2; ++i, ++g) {
  87. var p = potential(
  88. scale[0]*(x[0]+i)+shift[0],
  89. scale[1]*(x[1]+j)+shift[1],
  90. scale[2]*(x[2]+k)+shift[2]);
  91. grid[g] = p;
  92. mask |= (p < 0) ? (1<<g) : 0;
  93. }
  94. //Check for early termination if cell does not intersect boundary
  95. if(mask === 0 || mask === 0xff) {
  96. continue;
  97. }
  98. //Sum up edge intersections
  99. var edge_mask = edge_table[mask]
  100. , v = [0.0,0.0,0.0]
  101. , e_count = 0;
  102. //For every edge of the cube...
  103. for(var i=0; i<12; ++i) {
  104. //Use edge mask to check if it is crossed
  105. if(!(edge_mask & (1<<i))) {
  106. continue;
  107. }
  108. //If it did, increment number of edge crossings
  109. ++e_count;
  110. //Now find the point of intersection
  111. var e0 = cube_edges[ i<<1 ] //Unpack vertices
  112. , e1 = cube_edges[(i<<1)+1]
  113. , g0 = grid[e0] //Unpack grid values
  114. , g1 = grid[e1]
  115. , t = g0 - g1; //Compute point of intersection
  116. if(Math.abs(t) > 1e-6) {
  117. t = g0 / t;
  118. } else {
  119. continue;
  120. }
  121. //Interpolate vertices and add up intersections (this can be done without multiplying)
  122. for(var j=0, k=1; j<3; ++j, k<<=1) {
  123. var a = e0 & k
  124. , b = e1 & k;
  125. if(a !== b) {
  126. v[j] += a ? 1.0 - t : t;
  127. } else {
  128. v[j] += a ? 1.0 : 0;
  129. }
  130. }
  131. }
  132. //Now we just average the edge intersections and add them to coordinate
  133. var s = 1.0 / e_count;
  134. for(var i=0; i<3; ++i) {
  135. v[i] = scale[i] * (x[i] + s * v[i]) + shift[i];
  136. }
  137. //Add vertex to buffer, store pointer to vertex index in buffer
  138. buffer[m] = vertices.length;
  139. vertices.push(v);
  140. //Now we need to add faces together, to do this we just loop over 3 basis components
  141. for(var i=0; i<3; ++i) {
  142. //The first three entries of the edge_mask count the crossings along the edge
  143. if(!(edge_mask & (1<<i)) ) {
  144. continue;
  145. }
  146. // i = axes we are point along. iu, iv = orthogonal axes
  147. var iu = (i+1)%3
  148. , iv = (i+2)%3;
  149. //If we are on a boundary, skip it
  150. if(x[iu] === 0 || x[iv] === 0) {
  151. continue;
  152. }
  153. //Otherwise, look up adjacent edges in buffer
  154. var du = R[iu]
  155. , dv = R[iv];
  156. //Remember to flip orientation depending on the sign of the corner.
  157. if(mask & 1) {
  158. faces.push([buffer[m], buffer[m-du], buffer[m-dv]]);
  159. faces.push([buffer[m-dv], buffer[m-du], buffer[m-du-dv]]);
  160. } else {
  161. faces.push([buffer[m], buffer[m-dv], buffer[m-du]]);
  162. faces.push([buffer[m-du], buffer[m-dv], buffer[m-du-dv]]);
  163. }
  164. }
  165. }
  166. }
  167. //All done! Return the result
  168. return { positions: vertices, cells: faces };
  169. }
  170. export { surfaceNet }