API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/effects/stereoEffect.js

/**
 A **StereoEffect** sets up a stereo view for its {{#crossLink "Scene"}}Scene{{/crossLink}}.

 <a href="../../examples/#effects_stereo"><img src="../../assets/images/screenshots/StereoEffect.png"></img></a>

 ## Overview

 * Based on technique described in [this article by Paul Bourke](http://paulbourke.net/stereographics/stereorender/).

 ## Examples

 * [Stereo view using a StereoEffect](../../examples/#effects_stereo)

 ## Usage

 Stereo view of an {{#crossLink "Mesh"}}{{/crossLink}} using the {{#crossLink "Scene"}}Scene{{/crossLink}}'s default {{#crossLink "Camera"}}{{/crossLink}} and {{#crossLink "Viewport"}}{{/crossLink}}:

 ````javascript
 // Both the Mesh and the StereoEffect use their Scene's default Camera and Viewport

 var mesh = new xeogl.Mesh({
     geometry: new xeogl.TorusGeometry()
 });

 var stereoEffect = new xeogl.StereoEffect({
     fov: 45, // Default
     active: true // Default
 });
 ````

 @class StereoEffect
 @module xeogl
 @submodule effects
 @constructor
 @param [scene] {Scene} Parent {{#crossLink "Scene"}}Scene{{/crossLink}} - creates this StereoEffect in the default
 {{#crossLink "Scene"}}Scene{{/crossLink}} when omitted.
 @param [cfg] {*} Configs
 @param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}},
 generated automatically when omitted.
 @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this StereoEffect.
 @param [cfg.fov=45] Field-of-view angle in degrees.
 @param [cfg.active=true] {Boolean} Whether or not this StereoEffect is active.
 @extends Mesh
 */

xeogl.StereoEffect = class xeoglStereoEffect extends xeogl.Component {

    init(cfg) {
        super.init(cfg);
        this.fov = cfg.fov;
        this.active = cfg.active !== false;
    }

    /**
     * Field-of-view angle in degrees.
     *
     *
     * @property fov
     * @type Number
     * @default 45
     */
    set fov(value) {
        value = value || 45;
        if (this._fov === value) {
            return;
        }
        this._fov = value;
    }

    get fov() {
        return this._fov;
    }

    /**
     * Flag which indicates whether this StereoEffect is active or not.
     *
     * @property active
     * @type Boolean
     * @default true
     */
    set active(value) {
        value = !!value;
        if (this._active === value) {
            return;
        }
        this._active = value;
        this._active ? this._activate() : this._deactivate();
    }

    get active() {
        return this._active;
    }

    _activate() {

        var scene = this.scene;
        var camera = scene.camera;
        var viewport = scene.viewport;
        var frustum = camera.frustum;
        var canvas = scene.canvas;

        scene.passes = 2; // Two passes per render
        scene.clearEachPass = false; // Clear before first pass only
        camera.projection = "frustum"; // Camera in frustum projection mode
        viewport.autoBoundary = false; // Allow custom viewport boundary

        var math = xeogl.math;
        var eye = math.vec3();
        var look = math.vec3();
        var up = math.vec3();
        var eyeLook = math.vec3();
        var eyeVec = math.vec3();
        var sepVec = math.vec3();
        var leftEye = math.vec3();
        var leftLook = math.vec3();
        var rightEye = math.vec3();
        var rightLook = math.vec3();

        var self = this;

        this._onSceneRendering = scene.on("rendering", function (e) {

            var focalLength = -Math.abs(math.lenVec3(math.subVec3(camera.look, camera.eye, eyeLook)));
            var eyeSep = (1 / 30) * focalLength;
            var near = 0.1;
            var DTOR = 0.0174532925;
            var radians = DTOR * self._fov / 2;
            var wd2 = near * Math.tan(radians);
            var ndfl = near / focalLength;
            var canvasBoundary = canvas.boundary;
            var canvasWidth = canvasBoundary[2];
            var canvasHeight = canvasBoundary[3];
            var halfCanvasWidth = Math.round(canvasWidth / 2);
            var canvasAspectRatio = canvasWidth / canvasHeight;

            switch (e.pass) {

                case 0:

                    eye.set(camera.eye);
                    look.set(camera.look);
                    up.set(camera.up);

                    math.subVec3(look, eye, eyeVec);
                    math.cross3Vec3(up, eyeVec, sepVec);
                    math.normalizeVec3(sepVec);
                    math.mulVec3Scalar(sepVec, eyeSep / 2.0);

                    // Find left and right viewpoints

                    math.subVec3(eye, sepVec, leftEye);
                    math.subVec3(look, sepVec, leftLook);

                    math.addVec3(eye, sepVec, rightEye);
                    math.addVec3(look, sepVec, rightLook);

                    // Set view transform to left side

                    camera.eye = leftEye;
                    camera.look = leftLook;

                    // Set projection frustum to left half of view space

                    frustum.left = -canvasAspectRatio * wd2 - 0.5 * eyeSep * ndfl;
                    frustum.right = canvasAspectRatio * wd2 - 0.5 * eyeSep * ndfl;
                    frustum.top = wd2 * 2;
                    frustum.bottom = -wd2 * 2;

                    // Set viewport to left half of canvas

                    viewport.boundary = [0, 0, halfCanvasWidth, canvasHeight];

                    break;

                case 1:

                    // Set view transform to right side

                    camera.eye = rightEye;
                    camera.look = rightLook;

                    // Set projection frustum to left half of view space

                    frustum.left = -canvasAspectRatio * wd2 + 0.5 * eyeSep * ndfl;
                    frustum.right = canvasAspectRatio * wd2 + 0.5 * eyeSep * ndfl;
                    frustum.top = wd2 * 2;
                    frustum.bottom = -wd2 * 2;

                    // Set viewport to right half of canvas

                    viewport.boundary = [halfCanvasWidth, 0, halfCanvasWidth, canvasHeight];

                    break;
            }
        });

        // Intercept Scene after each render
        // After the second pass we'll restore the thispoint

        this._onSceneRendered = scene.on("rendered", function (e) {
            switch (e.pass) {
                case 1:
                    camera.eye = eye;
                    camera.look = look;
                    camera.up = up;
                    break;
            }
        });
    }

    _deactivate() {
        var scene = this.scene;
        scene.passes = 1; // Don't need to restore scene.clearEachPass
        scene.viewport.autoBoundary = true;
        scene.off(this._onSceneRendering);
        scene.off(this._onSceneRendered);
    }

    destroy() {
        super.destroy();
        this.active = false;
    }
};