Skip to content

⬅️ Back to Table of Contents

📄 StereoCamera.js

📊 Analysis Summary

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

📚 Table of Contents

🛠️ File Location:

📂 src/cameras/StereoCamera.js

📦 Imports

Name Source
Matrix4 ../math/Matrix4.js
DEG2RAD ../math/MathUtils.js
PerspectiveCamera ./PerspectiveCamera.js

Variables & Constants

Name Type Kind Value Exported
_eyeRight Matrix4 let/var new Matrix4()
_eyeLeft Matrix4 let/var new Matrix4()
_projectionMatrix Matrix4 let/var new Matrix4()
cache { focus: any; fov: any; aspect: any; ... let/var this._cache
needsUpdate boolean let/var cache.focus !== camera.focus \|\| cache.fov !== camera.fov \|\| cache.aspect ...
eyeSepHalf number let/var cache.eyeSep / 2
eyeSepOnProjection number let/var eyeSepHalf * cache.near / cache.focus
ymax number let/var ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom
xmin any let/var *not shown*
xmax any let/var *not shown*

Functions

StereoCamera.update(camera: PerspectiveCamera): void

JSDoc:

/**
     * Updates the stereo camera based on the given perspective camera.
     *
     * @param {PerspectiveCamera} camera - The perspective camera.
     */

Parameters:

  • camera PerspectiveCamera

Returns: void

Calls:

  • _projectionMatrix.copy
  • Math.tan
  • this.cameraL.projectionMatrix.copy
  • this.cameraR.projectionMatrix.copy
  • this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply
  • this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply

Internal Comments:

// Off-axis stereoscopic effect based on (x4)
// http://paulbourke.net/stereographics/stereorender/ (x4)
// translate xOffset (x5)
// for left eye (x3)
// for right eye (x3)

Code
update( camera ) {

        const cache = this._cache;

        const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov ||
            cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near ||
            cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep;

        if ( needsUpdate ) {

            cache.focus = camera.focus;
            cache.fov = camera.fov;
            cache.aspect = camera.aspect * this.aspect;
            cache.near = camera.near;
            cache.far = camera.far;
            cache.zoom = camera.zoom;
            cache.eyeSep = this.eyeSep;

            // Off-axis stereoscopic effect based on
            // http://paulbourke.net/stereographics/stereorender/

            _projectionMatrix.copy( camera.projectionMatrix );
            const eyeSepHalf = cache.eyeSep / 2;
            const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
            const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom;
            let xmin, xmax;

            // translate xOffset

            _eyeLeft.elements[ 12 ] = - eyeSepHalf;
            _eyeRight.elements[ 12 ] = eyeSepHalf;

            // for left eye

            xmin = - ymax * cache.aspect + eyeSepOnProjection;
            xmax = ymax * cache.aspect + eyeSepOnProjection;

            _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
            _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );

            this.cameraL.projectionMatrix.copy( _projectionMatrix );

            // for right eye

            xmin = - ymax * cache.aspect - eyeSepOnProjection;
            xmax = ymax * cache.aspect - eyeSepOnProjection;

            _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
            _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );

            this.cameraR.projectionMatrix.copy( _projectionMatrix );

        }

        this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft );
        this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight );

    }

Classes

StereoCamera

Class Code
class StereoCamera {

    /**
     * Constructs a new stereo camera.
     */
    constructor() {

        /**
         * The type property is used for detecting the object type
         * in context of serialization/deserialization.
         *
         * @type {string}
         * @readonly
         */
        this.type = 'StereoCamera';

        /**
         * The aspect.
         *
         * @type {number}
         * @default 1
         */
        this.aspect = 1;

        /**
         * The eye separation which represents the distance
         * between the left and right camera.
         *
         * @type {number}
         * @default 0.064
         */
        this.eyeSep = 0.064;

        /**
         * The camera representing the left eye. This is added to layer `1` so objects to be
         * rendered by the left camera must also be added to this layer.
         *
         * @type {PerspectiveCamera}
         */
        this.cameraL = new PerspectiveCamera();
        this.cameraL.layers.enable( 1 );
        this.cameraL.matrixAutoUpdate = false;

        /**
         * The camera representing the right eye. This is added to layer `2` so objects to be
         * rendered by the right camera must also be added to this layer.
         *
         * @type {PerspectiveCamera}
         */
        this.cameraR = new PerspectiveCamera();
        this.cameraR.layers.enable( 2 );
        this.cameraR.matrixAutoUpdate = false;

        this._cache = {
            focus: null,
            fov: null,
            aspect: null,
            near: null,
            far: null,
            zoom: null,
            eyeSep: null
        };

    }

    /**
     * Updates the stereo camera based on the given perspective camera.
     *
     * @param {PerspectiveCamera} camera - The perspective camera.
     */
    update( camera ) {

        const cache = this._cache;

        const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov ||
            cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near ||
            cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep;

        if ( needsUpdate ) {

            cache.focus = camera.focus;
            cache.fov = camera.fov;
            cache.aspect = camera.aspect * this.aspect;
            cache.near = camera.near;
            cache.far = camera.far;
            cache.zoom = camera.zoom;
            cache.eyeSep = this.eyeSep;

            // Off-axis stereoscopic effect based on
            // http://paulbourke.net/stereographics/stereorender/

            _projectionMatrix.copy( camera.projectionMatrix );
            const eyeSepHalf = cache.eyeSep / 2;
            const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
            const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom;
            let xmin, xmax;

            // translate xOffset

            _eyeLeft.elements[ 12 ] = - eyeSepHalf;
            _eyeRight.elements[ 12 ] = eyeSepHalf;

            // for left eye

            xmin = - ymax * cache.aspect + eyeSepOnProjection;
            xmax = ymax * cache.aspect + eyeSepOnProjection;

            _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
            _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );

            this.cameraL.projectionMatrix.copy( _projectionMatrix );

            // for right eye

            xmin = - ymax * cache.aspect - eyeSepOnProjection;
            xmax = ymax * cache.aspect - eyeSepOnProjection;

            _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
            _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );

            this.cameraR.projectionMatrix.copy( _projectionMatrix );

        }

        this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft );
        this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight );

    }

}

Methods

update(camera: PerspectiveCamera): void
Code
update( camera ) {

        const cache = this._cache;

        const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov ||
            cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near ||
            cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep;

        if ( needsUpdate ) {

            cache.focus = camera.focus;
            cache.fov = camera.fov;
            cache.aspect = camera.aspect * this.aspect;
            cache.near = camera.near;
            cache.far = camera.far;
            cache.zoom = camera.zoom;
            cache.eyeSep = this.eyeSep;

            // Off-axis stereoscopic effect based on
            // http://paulbourke.net/stereographics/stereorender/

            _projectionMatrix.copy( camera.projectionMatrix );
            const eyeSepHalf = cache.eyeSep / 2;
            const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
            const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom;
            let xmin, xmax;

            // translate xOffset

            _eyeLeft.elements[ 12 ] = - eyeSepHalf;
            _eyeRight.elements[ 12 ] = eyeSepHalf;

            // for left eye

            xmin = - ymax * cache.aspect + eyeSepOnProjection;
            xmax = ymax * cache.aspect + eyeSepOnProjection;

            _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
            _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );

            this.cameraL.projectionMatrix.copy( _projectionMatrix );

            // for right eye

            xmin = - ymax * cache.aspect - eyeSepOnProjection;
            xmax = ymax * cache.aspect - eyeSepOnProjection;

            _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
            _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );

            this.cameraR.projectionMatrix.copy( _projectionMatrix );

        }

        this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft );
        this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight );

    }