prePass/EnvironmentMap.js

import Base from '../core/Base';
import Vector3 from '../math/Vector3';
import PerspectiveCamera from '../camera/Perspective';
import FrameBuffer from '../FrameBuffer';

var targets = ['px', 'nx', 'py', 'ny', 'pz', 'nz'];

/**
 * Pass rendering scene to a environment cube map
 *
 * @constructor clay.prePass.EnvironmentMap
 * @extends clay.core.Base
 * @example
 *     // Example of car reflection
 *     var envMap = new clay.TextureCube({
 *         width: 256,
 *         height: 256
 *     });
 *     var envPass = new clay.prePass.EnvironmentMap({
 *         position: car.position,
 *         texture: envMap
 *     });
 *     var carBody = car.getChildByName('body');
 *     carBody.material.enableTexture('environmentMap');
 *     carBody.material.set('environmentMap', envMap);
 *     ...
 *     animation.on('frame', function(frameTime) {
 *         envPass.render(renderer, scene);
 *         renderer.render(scene, camera);
 *     });
 */
var EnvironmentMapPass = Base.extend(function() {
    var ret = /** @lends clay.prePass.EnvironmentMap# */ {
        /**
         * Camera position
         * @type {clay.Vector3}
         * @memberOf clay.prePass.EnvironmentMap#
         */
        position: new Vector3(),
        /**
         * Camera far plane
         * @type {number}
         * @memberOf clay.prePass.EnvironmentMap#
         */
        far: 1000,
        /**
         * Camera near plane
         * @type {number}
         * @memberOf clay.prePass.EnvironmentMap#
         */
        near: 0.1,
        /**
         * Environment cube map
         * @type {clay.TextureCube}
         * @memberOf clay.prePass.EnvironmentMap#
         */
        texture: null,

        /**
         * Used if you wan't have shadow in environment map
         * @type {clay.prePass.ShadowMap}
         */
        shadowMapPass: null,
    };
    var cameras = ret._cameras = {
        px: new PerspectiveCamera({ fov: 90 }),
        nx: new PerspectiveCamera({ fov: 90 }),
        py: new PerspectiveCamera({ fov: 90 }),
        ny: new PerspectiveCamera({ fov: 90 }),
        pz: new PerspectiveCamera({ fov: 90 }),
        nz: new PerspectiveCamera({ fov: 90 })
    };
    cameras.px.lookAt(Vector3.POSITIVE_X, Vector3.NEGATIVE_Y);
    cameras.nx.lookAt(Vector3.NEGATIVE_X, Vector3.NEGATIVE_Y);
    cameras.py.lookAt(Vector3.POSITIVE_Y, Vector3.POSITIVE_Z);
    cameras.ny.lookAt(Vector3.NEGATIVE_Y, Vector3.NEGATIVE_Z);
    cameras.pz.lookAt(Vector3.POSITIVE_Z, Vector3.NEGATIVE_Y);
    cameras.nz.lookAt(Vector3.NEGATIVE_Z, Vector3.NEGATIVE_Y);

    // FIXME In windows, use one framebuffer only renders one side of cubemap
    ret._frameBuffer = new FrameBuffer();

    return ret;
},  /** @lends clay.prePass.EnvironmentMap# */ {
    /**
     * @param  {string} target
     * @return  {clay.Camera}
     */
    getCamera: function (target) {
        return this._cameras[target];
    },
    /**
     * @param  {clay.Renderer} renderer
     * @param  {clay.Scene} scene
     * @param  {boolean} [notUpdateScene=false]
     */
    render: function(renderer, scene, notUpdateScene) {
        var _gl = renderer.gl;
        if (!notUpdateScene) {
            scene.update();
        }
        // Tweak fov
        // http://the-witness.net/news/2012/02/seamless-cube-map-filtering/
        var n = this.texture.width;
        var fov = 2 * Math.atan(n / (n - 0.5)) / Math.PI * 180;

        for (var i = 0; i < 6; i++) {
            var target = targets[i];
            var camera = this._cameras[target];
            Vector3.copy(camera.position, this.position);

            camera.far = this.far;
            camera.near = this.near;
            camera.fov = fov;

            if (this.shadowMapPass) {
                camera.update();

                // update boundingBoxLastFrame
                var bbox = scene.getBoundingBox();
                bbox.applyTransform(camera.viewMatrix);
                scene.viewBoundingBoxLastFrame.copy(bbox);

                this.shadowMapPass.render(renderer, scene, camera, true);
            }
            this._frameBuffer.attach(
                this.texture, _gl.COLOR_ATTACHMENT0,
                _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i
            );
            this._frameBuffer.bind(renderer);
            renderer.render(scene, camera, true);
            this._frameBuffer.unbind(renderer);
        }
    },
    /**
     * @param {clay.Renderer} renderer
     */
    dispose: function (renderer) {
        this._frameBuffer.dispose(renderer);
    }
});

export default EnvironmentMapPass;