0

I'm writing and animation loop using three.js and all the examples (mrdoob, stemkoski) I see online use unprotected globals at the beginning of the script. I tried to enclose these in the init() function and then pass them as arguments through the animation loop. However, the renderer is coming up undefined.

I'm not sure what I'm missing below. My primary question is how to understand best practice for setting up an animation loop with good closure (protecting variables that would otherwise be global). Thanks!

// THE MAIN ANIMATION LOOP:

// UPDATE the scene
function update(keyboard, controls, stats, clock) {

    // delta = change in time since last call (in seconds)
    var delta = clock.getDelta(); 

    // functionality provided by THREEx.KeyboardState.js    
    if ( keyboard.pressed("z") ) 
    { 
        // do something
    }

    controls.update();
    stats.update();

};

// RENDER the scene
function render(renderer, scene, camera) {

    renderer.render(scene, camera);

};

// ANIMATE the scene
function animate(scene, camera, renderer, controls, stats, keyboard, clock) {

    requestAnimationFrame(animate);
    render(renderer, scene, camera);        
    update(keyboard, controls, stats, clock);
};


// *********************
// INITIALIZES THE SCENE

function init(images) { // `images` is passed by a callback from loadImages

    // standard global variables, held privately
    var container, scene, camera, renderer, controls, stats;
    var keyboard = new THREEx.KeyboardState();
    var clock = new THREE.Clock();

    ///////////
    // SCENE //
    ///////////
    scene = new THREE.Scene();

    ////////////
    // CAMERA //
    ////////////

    // set the view size in pixels (custom or according to window size)
    var SCREEN_WIDTH = 1920, SCREEN_HEIGHT = 1080;
    // var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;     
    // camera attributes
    var VIEW_ANGLE = 20, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
    // set up camera
    camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
    // add the camera to the scene
    scene.add(camera);
    // the camera defaults to position (0,0,0)
    //  so pull it back (z = 400) and up (y = 100) and set the angle towards the scene origin
    // (x,y,z)
    camera.position.set(0,150,1000);
    camera.lookAt(scene.position);  

    //////////////
    // RENDERER //
    //////////////

    // create and start the renderer; choose antialias setting.
    if (Detector.webgl)
        renderer = new THREE.WebGLRenderer( {antialias:true} );
    else
        renderer = new THREE.CanvasRenderer(); 

    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);

    // attach div element to variable to contain the renderer
    container = document.getElementById( 'ThreeJS' );

    // attach renderer to the container div
    container.appendChild( renderer.domElement );

    ///////////
    // STATS //
    ///////////

    // displays current and past frames per second attained by scene
    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.bottom = '0px';
    stats.domElement.style.zIndex = 100;
    container.appendChild( stats.domElement );

    ///////////
    // LIGHT //
    ///////////

    // create a light
    var light = new THREE.PointLight(0xffffff);
    light.position.set(100,250,0);
    scene.add(light);

    ////////////
    // IMAGES //
    ////////////

    var element1 = THREE.ImageUtils.loadTexture(images.dresser10);
    var element2 = THREE.ImageUtils.loadTexture(images.dresser14);
    var element1Material = new THREE.SpriteMaterial( { map: element1, useScreenCoordinates: true, alignment: THREE.SpriteAlignment.topLeft  } );
    var sprite = new THREE.Sprite(element1Material);
    sprite.position.set( 50, 50, 0 );
    sprite.scale.set( 64, 64, 1.0 ); // imageWidth, imageHeight
    scene.add(sprite);



    animate(container, scene, camera, renderer, controls, stats, keyboard, clock);      
};


// ********************************************************
// CHECKS TO SEE IF THE WINDOW HAS LOADED BEFORE PROCEEDING
// Once the window is loaded, calls the init function

window.addEventListener ("load", eventWindowLoaded, false);
function eventWindowLoaded() {
    loadImages(init); // calls to initialize the scene once the images are loaded
}
gromiczek
  • 2,970
  • 5
  • 28
  • 49
  • 1
    If you want to avoid globals, just wrap the whole script in a module IEFE. `requestAnimationFrame(animate)` will not pass any parameters. – Bergi Mar 03 '14 at 23:15

1 Answers1

1

I followed @Bergi 's advice above and rewrote the animation loop in a Crockford style module that returns an object full of privileged methods that can access the now protected variables. Here it is for anyone looking for a similar pattern:

// ************************
// THE MAIN ANIMATION LOOP:

var animLoop = (function () {

    // standard global variables, held privately in this module
    var container, scene, camera, renderer, controls, stats;
    var keyboard = new THREEx.KeyboardState();
    var clock = new THREE.Clock();

    // SCENE
    scene = new THREE.Scene();

    // CAMERA
    var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;    
    var VIEW_ANGLE = 20, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
    camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
    scene.add(camera);
    camera.position.set(0,150,1000);
    camera.lookAt(scene.position);  

    // RENDERER
    if (Detector.webgl) {
        renderer = new THREE.WebGLRenderer( {antialias:true} );
    } else {
        renderer = new THREE.CanvasRenderer();
    } 
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    container = document.getElementById( 'ThreeJS' );
    container.appendChild( renderer.domElement );

    // LIGHT
    var light = new THREE.PointLight(0xffffff);
    light.position.set(100,250,0);
    scene.add(light);

    // IMAGES
    var images;
    var element1, element2, element1Material, sprite;   

    // RETURN:
    // *** returns an object full of functions with priviledged access to the private variables listed above ***
    return { 
        setImages: function (images_) { // sets the value of the images (array) above
            images = images_; 

        },
        createSprites: function () {
            var element1 = THREE.ImageUtils.loadTexture(images.dresser10.src);
            var element1Material = new THREE.SpriteMaterial( { map: element1, useScreenCoordinates: true, alignment: THREE.SpriteAlignment.topLeft  } );
            var sprite = new THREE.Sprite(element1Material);
            sprite.position.set( 50, 50, 0 );
            sprite.scale.set( 64, 64, 1.0 );
            scene.add(sprite);  
        },
        update: function () {
            var delta = clock.getDelta();
            // functionality provided by THREEx.KeyboardState.js    
            if ( keyboard.pressed("z") ) 
            { 
                // do something
            }

        },
        render: function () {
            renderer.render(scene, camera);
        }
    };
}());


// ANIMATE the scene
function animate() {
        requestAnimationFrame( animate );
        animLoop.render();  
        animLoop.update();
};

// INITIALIZES THE SCENE

function init(images) { // `images` is passed by a callback not included here
    animLoop.setImages(images); // places the initial array of images as a private variable in the animLoop object    
    animLoop.createSprites();
    animate();      

};

window.addEventListener ("load", eventWindowLoaded, false);
function eventWindowLoaded() {

    loadImages(init); // calls to initialize the scene once the images are loaded
};
gromiczek
  • 2,970
  • 5
  • 28
  • 49