Source: stage/Scene.js

/*jslint browser: true*/
/*globals Ayce*/

/**
 * Creates a new scene
 * @class
 * @param canvas
 * @constructor
 */
Ayce.Scene = function (canvas) {
    var scope = this;
    var i = 0;
    var recalcBuffers = true;
    var camera = new Ayce.Camera(new Ayce.CameraManager());
    var audioContext = new Ayce.AudioContext();
    var renderer = null;
    var keyboardHandler = new Ayce.KeyboardHandler();
    
    this.render = true;
    this.width = canvas.parentNode.clientWidth;
    this.height = canvas.parentNode.clientHeight;

    var objects = [];
    var transparentObjects = [];
    var lightContainer = new Ayce.LightContainer();
    var sounds = [];

//////////////////////////////////////////////////////////////////////////////////////
//Scene initialization

    /**
     * Should be called on window resize
     */
    this.resize = function () {
        this.width = canvas.parentNode.clientWidth;
        this.height = canvas.parentNode.clientHeight;
        renderer.width = this.width;
        renderer.height = this.height;
        renderer.resize();
        camera.width = renderer.getCanvasWidth();
        camera.height = renderer.getCanvasHeight();
        camera.updateProjectionMatrix();
    };

    /**
     * Returns true if WebVR compatible browser is being used
     * @returns {Boolean} isWebVR
     */
    this.useWebVR = function(){
        var m = camera.getManager().modifiers;
        if(m && m.length > 0)throw "Camera modifiers not empty. Please call camera.getManager().clearModifiers() first.";
        
        if(Ayce.HMDHandler.isWebVRReady()){
            var cameraController = camera.getManager();
            cameraController.modifiers.push(new Ayce.WebVR());
            camera.update();
            this.setRenderer(new Ayce.VRRenderer(canvas, false, cameraController));
            camera.useVR = true;
        }
        else{
            console.warn("Browser dosen't support WebVR.");
            return false;
        }
        return true;
    };

    /**
     * Call for VR rendering for Google Cardboard (and similar viewers). Parameter used to toggle barrel distortion and color abberation on and off.
     * @param {Boolean} distorted
     */
    this.useCardboard = function(distorted){
        this.useMotionSensor();
        this.setRendererVR(distorted);
        camera.useVR = true;
        this.resize();
    };

    /**
     * Sets up motion sensors as input for Google Cardboard (and similar viewers).
     */
    this.useMotionSensor = function(){
        var m = camera.getManager().modifiers;
        if(m && m.length > 0)throw "Camera modifiers not empty. Please call camera.getManager().clearModifiers() first.";
        m.push(new Ayce.Cardboard());
    };

    /**
     * Sets up rendering for desktop browsers
     */
    this.setRendererDesktop = function(){
        this.setRenderer(new Ayce.Renderer(canvas));
        camera.useVR = false;
        this.resize();
    };

    /**
     * Sets up rendering for VR on mobile browsers
     * @param {Boolean} distorted
     */
    this.setRendererVR = function(distorted){
        this.setRenderer(new Ayce.VRRenderer(canvas, distorted));
        camera.useVR = true;
        this.resize();
    };

    /**
     * Sets current renderer
     * @param {Ayce.Renderer} rendererObject
     */
    this.setRenderer = function(rendererObject){
        var shaders = null;
        if(renderer){
            rendererObject.clearColor = renderer.clearColor;
            shaders = renderer.getGL().shaders;
        }
        
        renderer = rendererObject;
        renderer.width = this.width;
        renderer.height = this.height;
        renderer.init();
        canvas.style.width = "auto";
        canvas.style.height = "auto";
        
        if(shaders){
            renderer.getGL().shaders = shaders;
        }
    };

    /**
     * Removes current renderer
     */
    this.setRendererNull = function(){
        renderer = null;
    };

    //setup scene
    this.setRendererDesktop();
    
    if(window.attachEvent) {
        window.attachEvent('onresize', this.resize);
    }
    else if(window.addEventListener) {
        window.addEventListener('resize', this.resize, true);
    }

//////////////////////////////////////////////////////////////////////////////////////
//Scene Management
    
    /**
     * Updates input, camera, lights, objects, renderer and sound
     */
    this.updateScene = function () {
        if(recalcBuffers){
            calcO3DBuffers(objects);
            calcO3DBuffers(transparentObjects);
            recalcBuffers = false;
        }
        
        //update keyboard inputHandlers
        keyboardHandler.update();
        
        //update camera
        camera.update();
        
        //update lights
        lightContainer.update(camera);
        
        //update objects
        for(i=0; i < objects.length; i++){
            objects[i].update();
        }
        for(i=0; i < transparentObjects.length; i++){
            transparentObjects[i].update();
        }
        renderer.update(camera, objects, transparentObjects);
        
        //update sounds
        for(i=0;i<sounds.length;i++){
            //sounds[i].listener = camera.getControls().position;
            sounds[i].listenerPosition = camera.getManager().getGlobalPosition();
            var orientation = camera.getManager().getGlobalRotation();
            var fwVector = orientation.getForwardVector();
            var upVector = orientation.getUpVector();
            sounds[i].listenerOrientationFront.x = fwVector.x;
            sounds[i].listenerOrientationFront.y = fwVector.y;
            sounds[i].listenerOrientationFront.z = fwVector.z;
            sounds[i].listenerOrientationUp.x = upVector.x;
            sounds[i].listenerOrientationUp.y = upVector.y;
            sounds[i].listenerOrientationUp.z = upVector.z;
            sounds[i].update();
        }
    };

    /**
     * Draws objects that have been added to the scene
     */
    this.drawScene = function () {
        if(this.render && ! recalcBuffers) {
            // Sort transparent objects for rendering
            var highestDistance = 0;
            for (var i = 0; i < transparentObjects.length; i++) {
                if (!transparentObjects[i].renderPriority) {     // Sort by distance to camera
                    var position = transparentObjects[i].getGlobalPosition();
                    var cc = camera.getManager();

                    transparentObjects[i].distance = Math.sqrt(
                        Math.pow(cc.getGlobalPosition().x - position.x, 2) +
                        Math.pow(cc.getGlobalPosition().y - position.y, 2) +
                        Math.pow(cc.getGlobalPosition().z - position.z, 2)
                    );

                    if (transparentObjects[i].distance > highestDistance) highestDistance = transparentObjects[i].distance;
                }
            }
            for (i = 0; i < transparentObjects.length; i++) {       // Sort by priority if available
                if (transparentObjects[i].renderPriority) {
                    if (transparentObjects[i].renderPriority > 0) {
                        transparentObjects[i].distance = highestDistance + transparentObjects[i].renderPriority;
                    } else {
                        transparentObjects[i].distance = transparentObjects[i].renderPriority;
                    }
                }
            }

            transparentObjects.sort(function (a, b) {
                return b.distance - a.distance;
            });

            //render scene
            renderer.render(camera, objects, transparentObjects);
        }
    };
    
    var calcO3DBuffers = function(o3DArray){
        for(i=0; i < o3DArray.length; i++){
            if(o3DArray[i].buffer){
                o3DArray[i].buffer.dispose();
            }
            o3DArray[i].buffer = renderer.getBuffer(o3DArray[i], lightContainer);
        }
    };

    /**
     * Adds light, object or sound to scene
     * @param {Ayce.Light|Ayce.Object3D|Ayce.Sound} object
     */
    this.addToScene = function (object) {
        
        //Add Light to Scene
        if (object instanceof Ayce.Light) {
            lightContainer.addLight(object);
            recalcBuffers = true;
        }
        
        //Add O3D to Scene
        else if (object instanceof Ayce.Object3D) {
            if(!recalcBuffers){
                object.buffer = renderer.getBuffer(object, lightContainer);
            }
            object.calcBoundingBox();
            object.calcBoundingSphere();

            if(object.transparent){
                transparentObjects.push(object);
            }
            else{
                objects.push(object);
            }
            
        }
        
        //Add Sound
        else if(object instanceof Ayce.Sound){
            object.init(audioContext);
            sounds.push(object);
        }
        
        //Unknown Object
        else {
            throw "Can't add to scene. Unknown type: " + typeof object;
        }
    };

    /**
     * Removes light, object or sound from scene
     * @param {Ayce.Light|Ayce.Object3D|Ayce.Sound} object
     */
    this.removeFromScene = function (object) {
        //Remove Light from Scene
        if (object instanceof Ayce.Light) {
            lightContainer.removeLight(object);
        }
        //Remove o3D from Scene
        else if (object instanceof Ayce.Object3D) {
            for (i=0; i < objects.length; i++) {
                if (objects[i] === object) {
                    objects.splice(i, 1);
                }
            }
            for (i=0; i < transparentObjects.length; i++) {
                if (transparentObjects[i] === object) {
                    transparentObjects.splice(i, 1);
                }
            }
        }
        else if(object instanceof Ayce.Sound){
            for (i=0; i < sounds.length; i++) {
                if (sounds[i] === object) {
                    sounds[i].stop();
                    sounds.splice(i, 1);
                }
            }
        }
        else {
            throw "Can't remove from scene: " + typeof object;
        }
    };
    
///////////////////////////////////////////////////////////////////////////////////////
//Getter / Setter

    /**
     * Returns camera object
     * @returns {Ayce.Camera} camera
     */
    this.getCamera = function () {
        return camera;
    };
    
    this.setClearColor = function(red, green, blue){
        renderer.clearColor.red = red;
        renderer.clearColor.green = green;
        renderer.clearColor.blue = blue;
        renderer.getGL().clearColor(red, green, blue, 1.0);
    };
    
    this.setAmbientLight = function(red, green, blue){
        lightContainer.ambientLight.red = red;
        lightContainer.ambientLight.green = green;
        lightContainer.ambientLight.blue = blue;
    };

};