Page 4: Primitive Objects and Basic Transformations

CS559 Spring 2023 Sample Solution

Objects

Objects in THREE are subclasses of Object3D. This same base class is used for all the things we put into the world: lights, camera, the actual “things” we see.

The nice part of this is that all objects get the same common functionality.

The main kind of “thing” we’ll put in the world is a Mesh. A Mesh basically is a collection of triangles (in the form of a Geometry) combined with a Material (that tells THREE how those triangles should look) and other information required to place them in the scene, such as transformations and hierarchy information.

To make a Mesh, we first need to create a Geometry object, and then we can create a Mesh by pairing it with a Material. THREE has many different kinds of Geometry objects built in (such as Boxes, Circles, Cones, Spheres, …); see the Documentation for the full list. We can also make our own Geometry by defining the triangles in the correct way. We’ll do that later in the class.

Box 1: Transformations

Objects in 3D have associated transformations as part of the Object3D class. You can read about it here.

Each object has its own transformation. The transformation is between the coordinate system of the object’s parent and the coordinate system of the object itself (so everything is relative). If we place the object in the world (by adding it directly to the Scene), the transformation is relative to the world coordinate system.

Each object has a matrix inside of it that stores its transformation. We can access that matrix. However, we usually don’t change the matrix directly: the matrix is computed as needed from the 3 transformations applied to the object: Translate, Scale, and Rotate.

One slightly confusing thing: objects have both “transformation” functions (that apply transformations to the current transformation) as well as the ability to set the position and/or rotation to a particular value. If you use a translate command, the position gets updated - and the way it is updated is affected by the current rotation. Or, you can just set the position to a particular value.

The way to think of this is to differentiate “transformation” (something that changes something) from state (where something is). So, a transformation modifies the state.

06-04-01

In this example in 06-04-01.js ( 06-04-01.html), the purple cube is first rotated by 45 degrees, and then its position is set (to have X = 2). The position given is the position the object gets. The red cube is also rotated 45 degrees, and then undergoes a translateX which causes it to be moved by 2 units along the X axis. But since the X axis has been rotated, the motion is along the direction of the rotated X axis. The translateX function (and its counterparts) all translate in the current coordinate system of the object.

The ability to either set positions and orientations or apply translations and rotations gives a lot of flexibility. Sometimes it’s easier to describe how you want the object to move, other times it’s easier to specify where you want it to be. Sometimes its confusing because THREE interprets each one in a different manner. To make things even more complicated, THREE is very flexible in how it handles transformations, allowing them to be specified in different ways and converting them between formats automatically. This can be convenient.

We’ll come back to explore transformations in 3D extensively in a later workbook.

Box 2: Loading Objects

If we want an object that isn’t built in, THREE gives us the ability to load it from a file.

A common file format for 3D models (as collections of triangles) is the obj file format. The THREE loader is part of their “examples” - we include it in the parts of THREE we provide for class, but the documentation isn’t very complete.

The trick with loading an object is that it may take time. So when we start the loader, we also provide a function that gets called when loading is finished. This function needs to set the object up (put it into the scene, position and scale it, …). Here’s an example in 06-04-02.js ( 06-04-02.html):

06-04-02

The relevant code:

33
34
35
36
37
38
39
40
let loader = new OBJLoader();
loader.load("./objects/07-astronaut.obj", function(astronaut) {
        astronaut.position.set(1.5, 4, 0);
        astronaut.scale.set(0.5, 0.5, 0.5);
        scene.add(astronaut);
        // note that we have to render
        renderer.render(scene, camera);
    });

Notice that this code creates a loader, and then asks the loader to load the file ./objects/07-astronaut.obj. In this call it passes another (anonymous) function that will be called when loading is complete. This function is passed the loaded object. Note how the function positions the object (setting its position), scales it, and places it into the scene. We didn’t have to rotate the object since it was already oriented the right way. Also, notice that we have to call render to draw the object after the object is loaded. If render is being called continuously (for example, in an animation loop), that can take care of making sure drawing happens after the object is loaded.

One other tricky thing: notice that the astronaut variable is a local variable. If you want to access the astronaut after it’s loaded (for example, to animate it), you need to have some way to access it from elsewhere in your program. For example, you might want to store it in a global variable. Be careful: remember that your code continues to run while the object is getting loaded.

Hint on Async Loading from Piazza 2022

Loading objects in THREE can be tricky. You can see some of the complexity in Workbook 6 page 4, where we load the astronaut. The workbook text explains some of it, but if you actually look into the file (06-04-02.js) you will see some more interesting things.

The first complexity is that load is asynchronous: it doesn’t happen instantly. In the workbook text, we set a callback for when the object is loaded and ready to be used. If you look into the code of 06-04-02.js, you will see two more “modern” ways to do this. If you don’t want to learn about Promises you don’t have to… it is totally fine to use the callback approach.

The workbook doesn’t explain these more modern ways to do asynchronous programming. We will discuss them in lecture (but maybe later than the workbook).

But, there are two other things to be aware of (spoiler alert, discovering these on your own is good practice learning to understand a program / API):

  1. Sometimes THREE requires you to tell it when you change an object. THREE aggressively avoids re-computing things - so you need to tell it when something changes so it knows to recompute. Matrices and materials are two examples.

  2. The loaders don’t always create the object types you think they will. Inspect what object you get (for example, look at the type of astronaut after it is loaded (lines 37 and 48). Use the debugger to place a break point (good practice!). You can do this for astro on line 60 as well. At the end of 06-04-02.js you will see examples that change the material to a yellow color.

In both cases, THREE is doing something smart. For 1, it is trying to be efficient. For 2, it allows loading in files that contain multiple meshes.

Box 3: Exercise 1, Stacking Boxes

In 06-04-03.js ( 06-04-03.html), there is a scene that has 5 boxes. Change the positions of the boxes so they are a stack (with the smaller boxes on top of the bigger boxes). Pay attention to both the initial sizes of the boxes and their scaling. The result should be a stack of 5 boxes on top of the ground (each box on top of the next larger box).

Now that we have objects, let’s discuss the cameras and lights on Page  5  (Lights, Camera, Action!).

Page 4 Rubric (5 points total)
Points (5):
Box 06-04-03
5 pt
stacking the boxes as described