CS 424: Computer Graphics, Fall 2015
Lab 9: Three.js

In this lab you will build complex models using three.js—an animated merry-go-round and at least one other model of your choice. You will need a copy of the folder lab9-files, which you can find in /classes/cs229. You will work on the file lab9.html. If you are using Komodo edit, you can create a new project in your copy of the folder, or you can copy the folder into an existing project directory. The sub-folder named resources contains a model of a horse that you will use in the merry-go-round. It also contains several image files that you might use as textures. You can add things to that folder if you want to use your own texture images.

As examples of building scenes in three.js, you have the Diskworld program that I handed out in class and the windmill and earth/moon that was discussed in class on Wednesday.

This lab is due next Thursday, November 5. If you have added anything to the "resources" folder, you should copy your entire lab9 folder to the homework directory; if not, it's sufficient to submit lab9.html. Remember that you should also meet with me to discuss your idea for a final project by November 5, and you should turn in a written project proposal in class on Friday, November 6.

The Assignment

Requirements... The first part of the assignment is to make a merry-go-round with at least five horses. The merry-go-round should have a top and a bottom and a pole for each horse. when animation is turned on, the merry-go-round should rotate, and the horses should go up and down. But the horses should not move in synchrony. In my version, there are two groups of four horses. One group is going up while the other is going down. (Each group was made using a THREE.Object3D, so that I could move the group as a unit.) You should use several different materials on the horses. You can, if you like, place an object at the center of the merry-go-round. For the second part of the assignment, you should build at least one more object. I chose to build something like the "cage" from Lab 4, but you can build whatever you like, as long as it is a complex object. It does not have to be animated. Your scene should use at least two texture images. Here is a screen shot of my version:

Some notes... There are issues with loading images and models in JavaScript, which are discussed below. But note in particular that Chrome will not load them from the local file system. Firefox will load them, as long as they are in a sub-directory in the same directory as the HTML file that uses them.

The cylinders that I use for the top and bottom of my merry-go-round have radius 10. Three.js cylinders have their centers at (0,0,0) and their axis lying along the y-axis. Note that the horse model is huge and will have to be scaled down. I applied a scaling factor of 0.02 to the horse.

You should start by building the merry-go-round without the horses and without any textures. Note that to get a pole into position, you need to first translate it to a point near the edge of the merry-go-round, then rotate it about the origin to get it into position. Since the transformations on a given object are applied in the order scale/rotate/translate, you will need an extra Object3D to do the rotation after the translation.

My poles have a subtle color variation that is due to a paisley texture applied to a yellow pole. Since the texture is rather stretched and distorted, it mainly just gives the poles a mottled appearance.

There is only one light in the original scene. You will probably want to add some additional lighting. I added two rather dim lights, a directional light shining from above and a point light in the center of the merry-go-round.

Once you have the basic object built, you can add code to the createWorld() function to start the process of loading the horse model and the texture images, as described in the next section.

The Asynchronous Loading Problem

Images and models for use with three.js are loaded asynchronously. That is, your code does something that starts the process of loading the resource file, but does not wait for the process to complete. Instead of waiting, you provide a callback function that the system will call when the loading has completed. When the callback function is called, the resource is ready for use. However, in the meantime, you have to cope with the fact that your scene is being drawn without the resource. One approach is not to draw anything, except maybe a progress bar, until all resources have been loaded. But that leaves the user with nothing to look at. Another approach is to display the scene with missing resources and add them when they arrive. You will use the latter approach in this lab.

Three.js provides some utility functions and classes to help with asynchronous loading. For loading a texture image, there is the function THREE.ImageUtils.loadTexture. I use it in the following method, which you can copy into your script:

function makeTexture(imageURL, material) {
    function callback() { // Function to react when image load is done.
        if (material) {
            material.map = texture;  // Add texture to material.
            material.needsUpdate = true;  // Required when material changes.
        }
        render();  // Render scene with texture that has just been loaded.
    }
    var texture = THREE.ImageUtils.loadTexture(imageURL, undefined, callback);
    return texture;
}

This function can be used in two ways. If you call it with two parameters, then the second parameter should be the material to which you want to apply the texture; the callback function will add the image texture to the material before rendering the scene. In the meantime, the material will just show its basic color. When you do things this way, it is not necessary to use the return value from the makeTexture() function.

If you call makeTexture() with just one parameter, then you will want to use the return value, which represents the Three.js texture object that has been created. You can use that on a material as you create the material. For example,

var materialWithBrickTexture = new THREE.MeshLambertMaterial({
    color: "white",
    map: makeTexture("resources/brick.jpg")
});

Note that although the Three.js texture object has been created by makeTexture, it does not yet have the image that will be used for the texture. While waiting for the image to load, the object will be black.

(Note, by the way, that you can use the same textured material on several objects. The image will appear on all of the objects that use the texture, once the image has been loaded. Also note that then when you clone an object, its material is not cloned. The clone shares the material with the original.)


As for models, Three.js has the class THREE.JSONLoader. An object of type JSONLoader had a load() method for loading models in a particular format used by Three.js. (There are other classes for loading models in other formats.) The horse model, which was part of the Three.js download, is in that format. You can use the following function, with parameter "resources/horse.js", to load the horse model. The horses should be can be created and added to the scene in the callback function. (Don't forget to scale the horse.)

function loadModel(modelURL) {
    function callback(geometry) {
        // To be executed when the model has fully loaded.  The parameter
        // is geometry that can be used for a THREE.Mesh object.
        
        render();  // Render the scene with the newly added objects.
        
    }
    var loader = new THREE.JSONLoader();
    try {
        loader.load(modelURL, callback);
    }
    catch (e) {
        // Note: Chrome gives an error if loading from local file system.
        console.log("Error loading model from " + modelURL);
    }
}

Note that Firefox will print a message to the console when the horse is loaded saying "not well-formed". This is not actually an error, and you should ignore it.

By the way, the horse geometry does come with texture coordinates, but they are not very useful. I tried making textured horses, but they didn't look good, so I went with solid colors. I suspect that the texture coordinates were designed to be used with a custom texture that would make the horse look more like a horse.