What is the best way to ensure data encapsulation (safeguarding global variables) in an

I'm currently working on an animation loop using three.js, and I've noticed that most online examples (like mrdoob, stemkoski) rely on unprotected globals at the beginning of the script. I attempted to encapsulate these in the init() function and then pass them as arguments through the animation loop. However, I'm encountering an issue where the renderer is returning as undefined.

I'm uncertain about what I might be overlooking in the code below. My main query revolves around grasping the best practices for setting up an animation loop with proper closure (safeguarding variables that could potentially be global). Any insights would be greatly appreciated!

// MAIN ANIMATION LOOP:

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

    var delta = clock.getDelta(); 

    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) { 

    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 = 1920, SCREEN_HEIGHT = 1080;
    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 );

    // STATS
    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.bottom = '0px';
    stats.domElement.style.zIndex = 100;
    container.appendChild( stats.domElement );

    // 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 ); 
    scene.add(sprite);

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

// WINDOW LOADED EVENT
window.addEventListener ("load", eventWindowLoaded, false);
function eventWindowLoaded() {
    loadImages(init); // calls to initialize the scene once the images are loaded
}

Answer №1

After taking advice from @Bergi mentioned above, I decided to revamp the animation loop by structuring it in a Crockford style module. This module now returns an object filled with privileged methods that have access to protected variables. For those interested in a similar pattern, check out the code below:

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

var animLoop = (function () {

    // private variables within 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 privileged 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
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Learn how to easily incorporate a drop-down list into the material-UI Search component within the navbar to enhance the search results

I integrated a Material UI search bar into my React app's navbar following the instructions from the official documentation on MUI. However, the article does not provide any guidance on how to add a dropdown list when selecting the search input field. ...

Establishing a secondary setTimeout function does not trigger the execution of JQUERY and AJAX

// Custom Cart Code for Database Quantity Update $('.input-text').on('keydown ' , function(){ var tr_parent = $(this).closest("tr"); setTimeout(function () { $(tr_parent).css('opacity', '0.3'); }, 4000); var i ...

Avoiding conflicts: Using Tabs with Revolution Slider without jQuery interference

I'm running into an issue with the tabs on my website. The revolution slider is working perfectly, but the tab widget seems to be displaying all the tab contents at once instead of each one separately. You can see the problem here: at the bottom of t ...

Sending a JSON array to a WebMethod

I encountered an issue when attempting to convert an object to a JSON array as a string, resulting in an Internal Server Error. Fortunately, the GetServerTime method is functioning properly. My goal is to send an array of objects to the server and conver ...

Deleting a sentence from a text document

Is there a way to remove a row from a text file without leaving empty lines? See the Example and Example2. Consider this scenario: Hello Hello2 String After deletion, the file should look like this: Hello Hello2 I attempted the following code but it re ...

"Utilize an Ajax form with the 'post' method

I'm facing an issue with the AJAX form as it keeps throwing an error. I really need it to function properly when a button is clicked, refreshing all data in the process. <form name="form" id="form" class="form"> ...

Uploading files with ExpressJS and AngularJS via a RESTful API

I'm a beginner when it comes to AngularJS and Node.js. My goal is to incorporate file (.pdf, .jpg, .doc) upload functionality using the REST API, AngularJS, and Express.js. Although I've looked at Use NodeJS to upload file in an API call for gu ...

Struggling to transfer information between POST and GET requests in Node/Express

Just diving into node/express, currently developing a weather application that receives location data from a form submission <form method="POST" action="/"> <input id="input" type="text" name="city" placeholder="Search by city or zip code" /> ...

Sequential cascade filtering without a preset default option

Please note: The following code snippet has been adjusted and is now functional. In a MySQL database table, I have the following columns with corresponding values: Category (Fruit, Vegetable) Type (Apple, Orange, Pumpkin, Potato) Subtype (Red Delicious, ...

Creating an interface that accurately infers the correct type based on the context

I have an example below of what I aim to achieve. My goal is to start with an empty list of DbTransactInput and then add objects to the array. I experimented with mapped types to ensure that the "Items" in the "Put" property infer the correct data type, w ...

How to deal with jQuery's set val() behavior on SELECT when there is no matching value

Let's say I have a select box like this: <select id="s" name="s"> <option value="0">-</option> <option value="1">A</option> <option value="2" selected>B</option> <option value="3">C</option> </ ...

Leverage require.js in combination with angular.js

Seeking assistance with require.js integration in Angular.js, encountering an error. Below is the code snippet: Configuration file: require.config({ paths: { angular: 'https://code.angularjs.org/1.5.5/angular.min', angularRo ...

Is it possible to identify the form triggering the ajax call within a callback function?

There are multiple forms on my website that share the same structure and classes. The objective is to submit form data to the server using the POST method, and display an error message if any issues arise. Here's how the HTML code for the forms look ...

What is the best way to implement restful instead of query syntax in a $resource factory?

Recently, I set up a $resource factory like this: (function() { 'use strict'; app.factory('WidgetFactory', [ '$resource', function($resource) { return $resource('http://localhost:8282/widget/:widgetId&apo ...

The continuous looping issue is being triggered when implementing a like button using firestore along with useEffect and UseState

I have developed a component to be loaded into various cards for displaying data. This particular component retrieves and stores data from the card (sale_id) onto the database. import { LikeButtonStyle } from './LikeButton.styled'; import { Image ...

Looking to retrieve the raw HTTP header string in Node.js using express.js?

Currently, I am utilizing Node.js and express.js for my work. The project I am currently working on requires me to access the raw strings of the HTTP headers (charset and accepted). In express.js, there is a function available that can provide the charset ...

Updating the object does not result in the interpolation value being updated as well

I am facing an issue with this v-for loop: <tr v-for="(product, index) in products" v-bind:key="products.id"> <div v-on:click="decrementQtd(index)" class="action-qtd-button-left"> <strong>-</strong> </div> < ...

Engaging with JSON data inputs

Need help! I'm attempting to fetch JSON data using AJAX and load it into a select control. However, the process seems to get stuck at "Downloading the recipes....". Any insights on what might be causing this issue? (Tried a few fixes but nothing has w ...

Transform socket.on into a promise

Currently, I am developing a service that involves the function init acting as resolve. Initially, it generates a folder using the connection's id and then proceeds to write some default files inside this newly created folder. The main goal here is to ...

This error message occurs when trying to access JSON keys from an object with an invalid operand in the 'in' operation

Check out the fiddle I created here: http://jsfiddle.net/kc11/h6nh1gvw/2/ I'm attempting to extract keys from a JSON string using this code: var keys = $.map(a, function(element,key) { return key; }); . But unfortunately, I keep encountering the er ...