Skip to content

⬅️ Back to Table of Contents

📄 PositionalAudioHelper.js

📊 Analysis Summary

Metric Count
🔧 Functions 3
🧱 Classes 1
📦 Imports 5
📊 Variables & Constants 18

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/helpers/PositionalAudioHelper.js

📦 Imports

Name Source
BufferGeometry three
BufferAttribute three
LineBasicMaterial three
Line three
MathUtils three

Variables & Constants

Name Type Kind Value Exported
geometry any let/var new BufferGeometry()
divisions number let/var divisionsInnerAngle + divisionsOuterAngle * 2
positions Float32Array<ArrayBuffer> let/var new Float32Array( ( divisions * 3 + 3 ) * 3 )
materialInnerAngle any let/var new LineBasicMaterial( { color: 0x00ff00 } )
materialOuterAngle any let/var new LineBasicMaterial( { color: 0xffff00 } )
audio PositionalAudio let/var this.audio
range number let/var this.range
divisionsInnerAngle number let/var this.divisionsInnerAngle
divisionsOuterAngle number let/var this.divisionsOuterAngle
halfConeInnerAngle number let/var coneInnerAngle / 2
halfConeOuterAngle number let/var coneOuterAngle / 2
start number let/var 0
count number let/var 0
i any let/var *not shown*
stride any let/var *not shown*
geometry any let/var this.geometry
positionAttribute any let/var geometry.attributes.position
step number let/var ( to - from ) / divisions

Functions

PositionalAudioHelper.update(): void

JSDoc:

/**
     * Updates the helper. This method must be called whenever the directional cone
     * of the positional audio is changed.
     */

Returns: void

Calls:

  • MathUtils.degToRad
  • geometry.clearGroups
  • positionAttribute.setXYZ
  • Math.sin
  • Math.cos
  • Math.min
  • geometry.addGroup
  • generateSegment

Internal Comments:

// (x8)

Code
update() {

        const audio = this.audio;
        const range = this.range;
        const divisionsInnerAngle = this.divisionsInnerAngle;
        const divisionsOuterAngle = this.divisionsOuterAngle;

        const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle );
        const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle );

        const halfConeInnerAngle = coneInnerAngle / 2;
        const halfConeOuterAngle = coneOuterAngle / 2;

        let start = 0;
        let count = 0;
        let i;
        let stride;

        const geometry = this.geometry;
        const positionAttribute = geometry.attributes.position;

        geometry.clearGroups();

        //

        function generateSegment( from, to, divisions, materialIndex ) {

            const step = ( to - from ) / divisions;

            positionAttribute.setXYZ( start, 0, 0, 0 );
            count ++;

            for ( i = from; i < to; i += step ) {

                stride = start + count;

                positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
                positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
                positionAttribute.setXYZ( stride + 2, 0, 0, 0 );

                count += 3;

            }

            geometry.addGroup( start, count, materialIndex );

            start += count;
            count = 0;

        }

        //

        generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 );
        generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 );
        generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 );

        //

        positionAttribute.needsUpdate = true;

        if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false;

    }

PositionalAudioHelper.dispose(): void

JSDoc:

/**
     * Frees the GPU-related resources allocated by this instance. Call this
     * method whenever this instance is no longer used in your app.
     */

Returns: void

Calls:

  • this.geometry.dispose
  • this.material[ 0 ].dispose
  • this.material[ 1 ].dispose
Code
dispose() {

        this.geometry.dispose();
        this.material[ 0 ].dispose();
        this.material[ 1 ].dispose();

    }

generateSegment(from: any, to: any, divisions: any, materialIndex: any): void

Parameters:

  • from any
  • to any
  • divisions any
  • materialIndex any

Returns: void

Calls:

  • positionAttribute.setXYZ
  • Math.sin
  • Math.cos
  • Math.min
  • geometry.addGroup
Code
function generateSegment( from, to, divisions, materialIndex ) {

            const step = ( to - from ) / divisions;

            positionAttribute.setXYZ( start, 0, 0, 0 );
            count ++;

            for ( i = from; i < to; i += step ) {

                stride = start + count;

                positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
                positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
                positionAttribute.setXYZ( stride + 2, 0, 0, 0 );

                count += 3;

            }

            geometry.addGroup( start, count, materialIndex );

            start += count;
            count = 0;

        }

Classes

PositionalAudioHelper

Class Code
class PositionalAudioHelper extends Line {

    /**
     * Constructs a new positional audio helper.
     *
     * @param {PositionalAudio} audio - The audio to visualize.
     * @param {number} [range=1] - The range of the directional cone.
     * @param {number} [divisionsInnerAngle=16] - The number of divisions of the inner part of the directional cone.
     * @param {number} [divisionsOuterAngle=2] The number of divisions of the outer part of the directional cone.
     */
    constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) {

        const geometry = new BufferGeometry();
        const divisions = divisionsInnerAngle + divisionsOuterAngle * 2;
        const positions = new Float32Array( ( divisions * 3 + 3 ) * 3 );
        geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );

        const materialInnerAngle = new LineBasicMaterial( { color: 0x00ff00 } );
        const materialOuterAngle = new LineBasicMaterial( { color: 0xffff00 } );

        super( geometry, [ materialOuterAngle, materialInnerAngle ] );

        /**
         * The audio to visualize.
         *
         * @type {PositionalAudio}
         */
        this.audio = audio;

        /**
         * The range of the directional cone.
         *
         * @type {number}
         * @default 1
         */
        this.range = range;

        /**
         * The number of divisions of the inner part of the directional cone.
         *
         * @type {number}
         * @default 16
         */
        this.divisionsInnerAngle = divisionsInnerAngle;

        /**
         * The number of divisions of the outer part of the directional cone.
         *
         * @type {number}
         * @default 2
         */
        this.divisionsOuterAngle = divisionsOuterAngle;

        this.type = 'PositionalAudioHelper';

        this.update();

    }

    /**
     * Updates the helper. This method must be called whenever the directional cone
     * of the positional audio is changed.
     */
    update() {

        const audio = this.audio;
        const range = this.range;
        const divisionsInnerAngle = this.divisionsInnerAngle;
        const divisionsOuterAngle = this.divisionsOuterAngle;

        const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle );
        const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle );

        const halfConeInnerAngle = coneInnerAngle / 2;
        const halfConeOuterAngle = coneOuterAngle / 2;

        let start = 0;
        let count = 0;
        let i;
        let stride;

        const geometry = this.geometry;
        const positionAttribute = geometry.attributes.position;

        geometry.clearGroups();

        //

        function generateSegment( from, to, divisions, materialIndex ) {

            const step = ( to - from ) / divisions;

            positionAttribute.setXYZ( start, 0, 0, 0 );
            count ++;

            for ( i = from; i < to; i += step ) {

                stride = start + count;

                positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
                positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
                positionAttribute.setXYZ( stride + 2, 0, 0, 0 );

                count += 3;

            }

            geometry.addGroup( start, count, materialIndex );

            start += count;
            count = 0;

        }

        //

        generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 );
        generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 );
        generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 );

        //

        positionAttribute.needsUpdate = true;

        if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false;

    }

    /**
     * Frees the GPU-related resources allocated by this instance. Call this
     * method whenever this instance is no longer used in your app.
     */
    dispose() {

        this.geometry.dispose();
        this.material[ 0 ].dispose();
        this.material[ 1 ].dispose();

    }

}

Methods

update(): void
Code
update() {

        const audio = this.audio;
        const range = this.range;
        const divisionsInnerAngle = this.divisionsInnerAngle;
        const divisionsOuterAngle = this.divisionsOuterAngle;

        const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle );
        const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle );

        const halfConeInnerAngle = coneInnerAngle / 2;
        const halfConeOuterAngle = coneOuterAngle / 2;

        let start = 0;
        let count = 0;
        let i;
        let stride;

        const geometry = this.geometry;
        const positionAttribute = geometry.attributes.position;

        geometry.clearGroups();

        //

        function generateSegment( from, to, divisions, materialIndex ) {

            const step = ( to - from ) / divisions;

            positionAttribute.setXYZ( start, 0, 0, 0 );
            count ++;

            for ( i = from; i < to; i += step ) {

                stride = start + count;

                positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
                positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
                positionAttribute.setXYZ( stride + 2, 0, 0, 0 );

                count += 3;

            }

            geometry.addGroup( start, count, materialIndex );

            start += count;
            count = 0;

        }

        //

        generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 );
        generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 );
        generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 );

        //

        positionAttribute.needsUpdate = true;

        if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false;

    }
dispose(): void
Code
dispose() {

        this.geometry.dispose();
        this.material[ 0 ].dispose();
        this.material[ 1 ].dispose();

    }