PositionalAudioHelper.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import {
  2. BufferGeometry,
  3. BufferAttribute,
  4. LineBasicMaterial,
  5. Line,
  6. MathUtils
  7. } from 'three';
  8. /**
  9. * This helper displays the directional cone of a positional audio.
  10. *
  11. * `PositionalAudioHelper` must be added as a child of the positional audio.
  12. *
  13. * ```js
  14. * const positionalAudio = new THREE.PositionalAudio( listener );
  15. * positionalAudio.setDirectionalCone( 180, 230, 0.1 );
  16. * scene.add( positionalAudio );
  17. *
  18. * const helper = new PositionalAudioHelper( positionalAudio );
  19. * positionalAudio.add( helper );
  20. * ```
  21. *
  22. * @augments Line
  23. * @three_import import { PositionalAudioHelper } from 'three/addons/helpers/PositionalAudioHelper.js';
  24. */
  25. class PositionalAudioHelper extends Line {
  26. /**
  27. * Constructs a new positional audio helper.
  28. *
  29. * @param {PositionalAudio} audio - The audio to visualize.
  30. * @param {number} [range=1] - The range of the directional cone.
  31. * @param {number} [divisionsInnerAngle=16] - The number of divisions of the inner part of the directional cone.
  32. * @param {number} [divisionsOuterAngle=2] The number of divisions of the outer part of the directional cone.
  33. */
  34. constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) {
  35. const geometry = new BufferGeometry();
  36. const divisions = divisionsInnerAngle + divisionsOuterAngle * 2;
  37. const positions = new Float32Array( ( divisions * 3 + 3 ) * 3 );
  38. geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
  39. const materialInnerAngle = new LineBasicMaterial( { color: 0x00ff00 } );
  40. const materialOuterAngle = new LineBasicMaterial( { color: 0xffff00 } );
  41. super( geometry, [ materialOuterAngle, materialInnerAngle ] );
  42. /**
  43. * The audio to visualize.
  44. *
  45. * @type {PositionalAudio}
  46. */
  47. this.audio = audio;
  48. /**
  49. * The range of the directional cone.
  50. *
  51. * @type {number}
  52. * @default 1
  53. */
  54. this.range = range;
  55. /**
  56. * The number of divisions of the inner part of the directional cone.
  57. *
  58. * @type {number}
  59. * @default 16
  60. */
  61. this.divisionsInnerAngle = divisionsInnerAngle;
  62. /**
  63. * The number of divisions of the outer part of the directional cone.
  64. *
  65. * @type {number}
  66. * @default 2
  67. */
  68. this.divisionsOuterAngle = divisionsOuterAngle;
  69. this.type = 'PositionalAudioHelper';
  70. this.update();
  71. }
  72. /**
  73. * Updates the helper. This method must be called whenever the directional cone
  74. * of the positional audio is changed.
  75. */
  76. update() {
  77. const audio = this.audio;
  78. const range = this.range;
  79. const divisionsInnerAngle = this.divisionsInnerAngle;
  80. const divisionsOuterAngle = this.divisionsOuterAngle;
  81. const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle );
  82. const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle );
  83. const halfConeInnerAngle = coneInnerAngle / 2;
  84. const halfConeOuterAngle = coneOuterAngle / 2;
  85. let start = 0;
  86. let count = 0;
  87. let i;
  88. let stride;
  89. const geometry = this.geometry;
  90. const positionAttribute = geometry.attributes.position;
  91. geometry.clearGroups();
  92. //
  93. function generateSegment( from, to, divisions, materialIndex ) {
  94. const step = ( to - from ) / divisions;
  95. positionAttribute.setXYZ( start, 0, 0, 0 );
  96. count ++;
  97. for ( i = from; i < to; i += step ) {
  98. stride = start + count;
  99. positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
  100. positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
  101. positionAttribute.setXYZ( stride + 2, 0, 0, 0 );
  102. count += 3;
  103. }
  104. geometry.addGroup( start, count, materialIndex );
  105. start += count;
  106. count = 0;
  107. }
  108. //
  109. generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 );
  110. generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 );
  111. generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 );
  112. //
  113. positionAttribute.needsUpdate = true;
  114. if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false;
  115. }
  116. /**
  117. * Frees the GPU-related resources allocated by this instance. Call this
  118. * method whenever this instance is no longer used in your app.
  119. */
  120. dispose() {
  121. this.geometry.dispose();
  122. this.material[ 0 ].dispose();
  123. this.material[ 1 ].dispose();
  124. }
  125. }
  126. export { PositionalAudioHelper };