Book Image

Three.js Cookbook

By : Jos Dirksen
Book Image

Three.js Cookbook

By: Jos Dirksen

Overview of this book

Table of Contents (15 chapters)
Three.js Cookbook
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Waiting until resources are loaded


In the Load resources asynchronously recipe, we showed how you can load external Three.js resources asynchronously. For many sites and visualization, loading resources asynchronously is a good approach. Sometimes, however, you want to make sure that all the resources you require in your scene have been loaded beforehand. For instance, when you're creating a game, you might want to load all the data for a specific level beforehand. A common method of loading resources synchronously is nesting the asynchronous callbacks we've seen in the previous recipe. This, however, quickly becomes unreadable and very hard to manage. In this recipe, we'll use a different approach and work with a JavaScript library called Q.

Getting ready

As for all the external libraries that we use, we need to include the Q library in our HTML. You can download the latest version of this library from its GitHub repository at https://github.com/kriskowal/q, or use the version provided in the libs folder in the sources for this book. To include this library in your HTML page, add the following in the head element of your HTML page:

    <script src="../libs/q.js"></script>

In the sources for this chapter, you can also find an example where we load resources synchronously. Open 01.12-wait-for-resources.html in your browser and open the JavaScript console:

On the console output, you'll see that the required resources and models are loaded one after another.

How to do it...

  1. Let's first take a look at what we're aiming for in this recipe. We want to load resources synchronously, using the Q library, in the following manner:

        loadModel(model)
          .then(function(result) {return loadTexture(texture)})
          .then(function(result) {return loadModel(m)})
          .then(function(result) {return loadTexture(texture)})
          .then(function(result) {return loadOthers(resource)})
          .then(function(result) {return loadModelWithProgress(m)})
          .then(function(result) {return loadModel(model)})
          .then(function(result) {return loadOthers(resource)})
          .then(function(result) {return loadModel(model)})
          .then(function() {console.log("All done with sequence")})
          .catch(function(error) {
            console.log("Error occurred in sequence:",error);
          })
          .progress(function(e){
            console.log("Progress event received:", e);
           });
  2. What this code fragment means is that:

    1. Firstly, we want to call loadModel(model).

    2. Once the model is loaded, we load, using the then function, a texture using the loadTexture(texture) function. Once this texture is loaded, we will then load the next resource and so on. In this code fragment, you can also see that we also call a catch and a progress function. If an error occurs during loading, the function provided to catch() will be called. The same goes for progress(). If one of the methods wants to provide information about its progress, the function passed into progress() will be called.

    3. However, you will then find out that this won't work with the functions from our previous recipe. To get this to work, we have to replace the callbacks from these functions with a special Q construct that is called a deferred function:

          function loadTexture(texture) {
      
            var deferred = Q.defer();
            var text = THREE.ImageUtils.loadTexture
            (texture, null, function(loaded) {
              console.log("Loaded texture: ", texture);
              deferred.resolve(loaded);
            }, function(error) {
              deferred.reject(error);
            });
      
            return deferred.promise;
          }
    4. In this code snippet, we create a new JavaScript object with the name deferred. The deferred object will make sure that the results of the callbacks, this time defined as anonymous functions, are returned in such a way that we can use the then function we saw at the beginning of this chapter. If the resource is loaded successfully, we use the deferred.resolve function to store the result; if the resource was loaded unsuccessfully, we store the error using the deferred.reject function.

    5. We use the same approach for the loadModel, loadOthers, and loadModelWithProgress functions:

          function loadModel(model) {
      
            var deferred = Q.defer();
            var jsonLoader = new THREE.JSONLoader();
            jsonLoader.load(model, function(loaded) {
              console.log("Loaded model: ", model);
              deferred.resolve(loaded);
            }, null);
      
            return deferred.promise;
          }
      
          function loadOthers(res) {
            var deferred = Q.defer();
      
            var xhrLoader = new THREE.XHRLoader();
            xhrLoader.load(res, function(loaded) {
              console.log("Loaded other: ", res);
              deferred.resolve(loaded);
            }, function(progress) {
              deferred.notify(progress);
            }, function(error) {
              deferred.reject(error);
            });
      
            return deferred.promise;
          }
    6. In the loadOthers function, we are also provided with the progress information. To make sure that the progress callback is handled correctly, we use the deferred.notify() function and pass in the progress object:

          function loadModelWithProgress(model) {
            var deferred = Q.defer();
      
            var jsonLoader = new THREE.JSONLoader();
            jsonLoader.loadAjaxJSON(jsonLoader, model,
            function(model) {
              console.log("Loaded model with progress: ", model);
              deferred.resolve(model)
            }, null,
            function(progress) {
              deferred.notify(progress)
            });
      
            return deferred.promise;
          }
    7. With these changes, we can now load the resources synchronously.

How it works...

To understand how this works, you have to understand what Q does. Q is a promises library. With promises, you can replace the nested callbacks (also called the Pyramid of doom at http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/) with simple steps. The following example for the Q site nicely shows what this accomplishes:

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // Do something with value4
      });
    });
  });
});

Using promises, we can flatten this to the following (just like we did in this recipe):

Q.fcall(promisedStep1)
then(promisedStep2)
then(promisedStep3)
then(promisedStep4)
then(function (value4) {
  // Do something with value4
})
catch(function (error) {
  // Handle any error from all above steps
})
done();

If we were to rewrite the Three.js library, we could have used promises in Three.js internally, but since Three.js already uses callbacks, we had to use the Q.defer() function provided by Q to convert these callbacks to promises.

There is more...

We only touched a small part of what is possible with the Q promises library. We used it for synchronous loading, but Q has many other useful features. A very good starting point is the Q wiki available at https://github.com/kriskowal/q/wiki.

See also

  • Just like every recipe that loads resources you have to make sure that you run it either with a local web server, see the Setting up a local web server using Python recipe or the Setting up a web server using Node.js recipe, or disable some security settings (see the Solving cross-origin-domain error messages in Chrome recipe or the Solving cross-origin-domain error messages in Firefox recipe). If you want to load resources asynchronously, you can take a look at the Load any resource asynchronously recipe.