CS 424: Computer Graphics, Fall 2017
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/cs424. 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 two JavaScript files used by the program and 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 from the textbook and the windmill and earth/moon that was discussed in class on Wednesday.

This lab is due next Thursday, November 2. I will look for a file named "lab9.html" in your homework folder. If you have added anything to the "resources" folder, you need to copy your entire lab9 folder to the homework directory; if not, it would be sufficient to submit just the file lab9.html.

Remember that you should also meet with me to discuss your idea for a final project by November 2, and you should turn in a written project proposal in class on Friday, November 3. The written proposal should not be just a statement of your project topic. Please include information about the design of the project and resources that you have consulted or plan to use, including bibliography information. A one-page proposal would probably be sufficient, but a half-page is probably too short.

The Assignment

Requirements... The first part of the assignment is to make a merry-go-round with at least six horses. The merry-go-round must have a top and a bottom and a pole for each horse. The horses should not all have the same material. When animation is turned on, the merry-go-round must rotate, and the horses must 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 of four horses 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 in arder to do a rotation after the translation. (This is similar to the moon in windmill.html.)

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 at 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 class THREE.TextureLoader. I use it in the following method, which you can copy into your script:

function makeTexture(imageURL, material) {
    function callback() {
        if (material) {
            material.map = texture;
            material.needsUpdate = true;
        }
        render();  // render scene with texture that has just been loaded.
    }
    var loader = new THREE.TextureLoader();
    var texture = loader.load(imageURL, 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 when you clone an object, the cloned object uses the same material object, not a copy. The material object is not cloned.)


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 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 model comes with some colors. In order to use those colors on a horse, you need to set the value of the property vertexColors to THREE.FaceColors in the material that you use on the mesh. For example, create the material with:

material = new THREE.MeshLambertMaterial({vertexColors: THREE.FaceColors});