MeshSurfaceSampler.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import {
  2. Triangle,
  3. Vector2,
  4. Vector3
  5. } from 'three';
  6. /**
  7. * Utility class for sampling weighted random points on the surface of a mesh.
  8. *
  9. * Building the sampler is a one-time O(n) operation. Once built, any number of
  10. * random samples may be selected in O(logn) time. Memory usage is O(n).
  11. *
  12. * References:
  13. * - http://www.joesfer.com/?p=84
  14. * - https://stackoverflow.com/a/4322940/1314762
  15. */
  16. const _face = new Triangle();
  17. const _color = new Vector3();
  18. const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2();
  19. class MeshSurfaceSampler {
  20. constructor( mesh ) {
  21. this.geometry = mesh.geometry;
  22. this.randomFunction = Math.random;
  23. this.indexAttribute = this.geometry.index;
  24. this.positionAttribute = this.geometry.getAttribute( 'position' );
  25. this.normalAttribute = this.geometry.getAttribute( 'normal' );
  26. this.colorAttribute = this.geometry.getAttribute( 'color' );
  27. this.uvAttribute = this.geometry.getAttribute( 'uv' );
  28. this.weightAttribute = null;
  29. this.distribution = null;
  30. }
  31. setWeightAttribute( name ) {
  32. this.weightAttribute = name ? this.geometry.getAttribute( name ) : null;
  33. return this;
  34. }
  35. build() {
  36. const indexAttribute = this.indexAttribute;
  37. const positionAttribute = this.positionAttribute;
  38. const weightAttribute = this.weightAttribute;
  39. const totalFaces = indexAttribute ? ( indexAttribute.count / 3 ) : ( positionAttribute.count / 3 );
  40. const faceWeights = new Float32Array( totalFaces );
  41. // Accumulate weights for each mesh face.
  42. for ( let i = 0; i < totalFaces; i ++ ) {
  43. let faceWeight = 1;
  44. let i0 = 3 * i;
  45. let i1 = 3 * i + 1;
  46. let i2 = 3 * i + 2;
  47. if ( indexAttribute ) {
  48. i0 = indexAttribute.getX( i0 );
  49. i1 = indexAttribute.getX( i1 );
  50. i2 = indexAttribute.getX( i2 );
  51. }
  52. if ( weightAttribute ) {
  53. faceWeight = weightAttribute.getX( i0 )
  54. + weightAttribute.getX( i1 )
  55. + weightAttribute.getX( i2 );
  56. }
  57. _face.a.fromBufferAttribute( positionAttribute, i0 );
  58. _face.b.fromBufferAttribute( positionAttribute, i1 );
  59. _face.c.fromBufferAttribute( positionAttribute, i2 );
  60. faceWeight *= _face.getArea();
  61. faceWeights[ i ] = faceWeight;
  62. }
  63. // Store cumulative total face weights in an array, where weight index
  64. // corresponds to face index.
  65. const distribution = new Float32Array( totalFaces );
  66. let cumulativeTotal = 0;
  67. for ( let i = 0; i < totalFaces; i ++ ) {
  68. cumulativeTotal += faceWeights[ i ];
  69. distribution[ i ] = cumulativeTotal;
  70. }
  71. this.distribution = distribution;
  72. return this;
  73. }
  74. setRandomGenerator( randomFunction ) {
  75. this.randomFunction = randomFunction;
  76. return this;
  77. }
  78. sample( targetPosition, targetNormal, targetColor, targetUV ) {
  79. const faceIndex = this.sampleFaceIndex();
  80. return this.sampleFace( faceIndex, targetPosition, targetNormal, targetColor, targetUV );
  81. }
  82. sampleFaceIndex() {
  83. const cumulativeTotal = this.distribution[ this.distribution.length - 1 ];
  84. return this.binarySearch( this.randomFunction() * cumulativeTotal );
  85. }
  86. binarySearch( x ) {
  87. const dist = this.distribution;
  88. let start = 0;
  89. let end = dist.length - 1;
  90. let index = - 1;
  91. while ( start <= end ) {
  92. const mid = Math.ceil( ( start + end ) / 2 );
  93. if ( mid === 0 || dist[ mid - 1 ] <= x && dist[ mid ] > x ) {
  94. index = mid;
  95. break;
  96. } else if ( x < dist[ mid ] ) {
  97. end = mid - 1;
  98. } else {
  99. start = mid + 1;
  100. }
  101. }
  102. return index;
  103. }
  104. sampleFace( faceIndex, targetPosition, targetNormal, targetColor, targetUV ) {
  105. let u = this.randomFunction();
  106. let v = this.randomFunction();
  107. if ( u + v > 1 ) {
  108. u = 1 - u;
  109. v = 1 - v;
  110. }
  111. // get the vertex attribute indices
  112. const indexAttribute = this.indexAttribute;
  113. let i0 = faceIndex * 3;
  114. let i1 = faceIndex * 3 + 1;
  115. let i2 = faceIndex * 3 + 2;
  116. if ( indexAttribute ) {
  117. i0 = indexAttribute.getX( i0 );
  118. i1 = indexAttribute.getX( i1 );
  119. i2 = indexAttribute.getX( i2 );
  120. }
  121. _face.a.fromBufferAttribute( this.positionAttribute, i0 );
  122. _face.b.fromBufferAttribute( this.positionAttribute, i1 );
  123. _face.c.fromBufferAttribute( this.positionAttribute, i2 );
  124. targetPosition
  125. .set( 0, 0, 0 )
  126. .addScaledVector( _face.a, u )
  127. .addScaledVector( _face.b, v )
  128. .addScaledVector( _face.c, 1 - ( u + v ) );
  129. if ( targetNormal !== undefined ) {
  130. if ( this.normalAttribute !== undefined ) {
  131. _face.a.fromBufferAttribute( this.normalAttribute, i0 );
  132. _face.b.fromBufferAttribute( this.normalAttribute, i1 );
  133. _face.c.fromBufferAttribute( this.normalAttribute, i2 );
  134. targetNormal.set( 0, 0, 0 ).addScaledVector( _face.a, u ).addScaledVector( _face.b, v ).addScaledVector( _face.c, 1 - ( u + v ) ).normalize();
  135. } else {
  136. _face.getNormal( targetNormal );
  137. }
  138. }
  139. if ( targetColor !== undefined && this.colorAttribute !== undefined ) {
  140. _face.a.fromBufferAttribute( this.colorAttribute, i0 );
  141. _face.b.fromBufferAttribute( this.colorAttribute, i1 );
  142. _face.c.fromBufferAttribute( this.colorAttribute, i2 );
  143. _color
  144. .set( 0, 0, 0 )
  145. .addScaledVector( _face.a, u )
  146. .addScaledVector( _face.b, v )
  147. .addScaledVector( _face.c, 1 - ( u + v ) );
  148. targetColor.r = _color.x;
  149. targetColor.g = _color.y;
  150. targetColor.b = _color.z;
  151. }
  152. if ( targetUV !== undefined && this.uvAttribute !== undefined ) {
  153. _uva.fromBufferAttribute( this.uvAttribute, i0 );
  154. _uvb.fromBufferAttribute( this.uvAttribute, i1 );
  155. _uvc.fromBufferAttribute( this.uvAttribute, i2 );
  156. targetUV.set( 0, 0 ).addScaledVector( _uva, u ).addScaledVector( _uvb, v ).addScaledVector( _uvc, 1 - ( u + v ) );
  157. }
  158. return this;
  159. }
  160. }
  161. export { MeshSurfaceSampler };