Page 7: The CS559 Framework Code (GraphicsTown)

CS559 Spring 2023 Sample Solution

Prelude to Graphics Town

For the remaining parts of the workbook (two exercises), you will work with some framework code we have created for this class. Learning to work with the Framework code is important practically (you will use it on most of the assignments from here on), but also pedagogically. In the real world, you often have to work with code that someone else wrote.

The framework code allows you to focus on creating graphics objects and defining their behavior (for animation). You don’t need to worry about setting up a user interface, or the basic stuff of the world. It will give you “good enough” defaults that you can focus on the parts of the assignment you care about. For example, in this assignment, you can focus on making hierarchical objects. In future workbooks you will focus on making appearances (just wait).

The framework provides a “thin wrapper” around THREE.JS - you still program using THREE.JS, but you put your objects inside of wrapper objects, and define their behaviors as methods of these objects.

You will use the framework code for the rest of the assignments in class, so it is worth learning about it.

The best documentation for the framework is the code itself, and the examples that we give you. The code is designed to be (reasonably) self-documenting. We are trying to structure the comments to automatically produce a documentation web (using jsdoc), but that only gets some of the information. Here is a link to the Framework Documentation.

The framework is really helpful for small exercises where we simply want you to make an object. It will let you put the object on the screen with simple lighting and a simple UI with very little code. However, the real point of the framework is to allow you make a world with lots of objects that are all defined independently. This will let you make much more complex worlds. It also lets you add objects that we give you (or even to share objects with other students).

The framework will allow us to build “Graphics Town” - where you will create a world with lots of “living” stuff in it. The last workbooks/assignments of the semester will ask you to do this. The new assignments will ask you to make some objects and put them into simpler “worlds”. However, the objects you create can be re-used later in later assignments.

If you’re wondering where the “fancy features” of the framework are (shadows, multi-pass rendering, or other fancy things), just wait. As we learn things in class, we will give you extended versions of the framework that support these kinds of things.

Modern JavaScript

Learning about features of “modern” JavaScript is a learning goal of this assignment.

The framework code uses “modern” JavaScript. That includes modules and classes. We’ve actually had some of that in prior assignments, but this time you will see more of it. You will not be able to avoid writing a class or figuring out how to export from a module. Fortunately, we will provide a lot of examples, so you can always do things by modifying something we’ve given you to start with. However, we do recommend that you take some time to actually understand what is going on with the code.

If you want to learn about JavaScript classes, we have a basic tutorial for class Traditional Object Oriented Programming in JavaScript. To learn more about them (and other nice JavaScript features), most up-to-date books have discussions. See the Javascript in CS559 for suggestions. One specific recommendation: Exploring JS Ch15 has a nice introduction before it starts a deep-dive into some gory details and tricks to do fancy stuff.

Box 1: Object3D and GrObject

Here’s a simple example in 07-07-01.js ( 07-07-01.html).

Notice that this example has a ground object (the big flat gray box), it has lights, it has a camera in a reasonable location, it has orbit controls. You know how to do all that in THREE. So now, we can use the framework to do it for us.

The code is a bit simpler than if you had to write it yourself in THREE (this is an excerpt from 07-07-01.js:

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
let world = new GrWorld({
    groundplanecolor: "gray",
    where: document.getElementById("div1")
});

// make a THREE cube
let geometry = new T.BoxGeometry(1, 1, 1);
let mat = new T.MeshStandardMaterial({ color: "green" });
let cubeObj3D = new T.Mesh(geometry, mat);

// make this cubeObj3D (an Object3D) into a framework object
let cube1 = new GrObject("cube-0",cubeObj3D);
// put it into the world
world.add(cube1);
// move it above the ground plane
cubeObj3D.position.y=0.5;

// make a Framework object that has a cube inside of it
let cube2 = new GrCube({x:2,y:0.5,color:"yellow"});
world.add(cube2);

// run the animation/interaction loop
world.go();

On line 12, we create a GrWorld object. This object takes the place of the scene and renderer in THREE (or, to be more precise, it creates a scene and a renderer and stores it inside). In fact, the “world” sets up many things for us:

  1. it creates a Scene
  2. it creates a Renderer for us - since the Renderer will create a canvas (DOM element) for us, we need to say where we want it to go. If we say nothing, it sticks it at the end of the DOM. Here, we find a <div> and put it in there.
  3. it creates some lights - if we don’t like those lights, we can tell it not to
  4. it creates a camera and places it in a reasonable location
  5. it creates a “groundplane” - here, we change its color to gray (by default, it’s green).
  6. it does some other stuff too - you can read the code to see what happens. some of the things are more useful when the get to future assignments

Normally, we don’t need to worry about the THREE objects inside of the world, we just use the GrWorld.

By now, you should be familiar with THREE’s Object3D - the base class of objects you can put in the world. These objects can be meshes (and have geometry), they have transformations, you put them into the THREE.js scene, etc.

The Framework defines GrObject - which contains a list of THREE Object3D (these are stored in the objects member variable). We add GrObjects to the GrWorld.

For example, on lines 18-20 we make a THREE.js cube (a mesh with a box geometry and a material). Then on line 23, we make a GrObject that holds this cube. On 25, we add the cube to the GrWorld (Note: this takes care of adding the internal cube to the THREE Scene).

We can define subclasses of GrObject that create different kinds of objects (by creating the appropriate THREE.js objects). Some of these are built in. For example, there is a GrCube that, well, you can guess. See the example on line 30.

The THREE objects are inside of the GrObject. If we looked at cube1.objects[0] we’d find the Mesh we made on line 20. If we looked at cube2.objects[0] we’d find a similar Mesh created insider the GrCube constructor.

On line 34, we call go() - a method of GrWorld that starts an infinite loop with a render loop (using window.requestAnimationFrame).

Read the code or documentation to see all of the options for these objects.

Box 2: Getting Started with a simple example

07-07-02.js ( 07-07-02.html) is another spinning cube example (from Page  3  (Axis Angle Representations) and the previous workbook and lecture). This time in the framework:

Everything is pretty much the default - except that we made the groundplane gray, not its usual green, since the cube is green. We got some lighting, a camera, orbit controls, an animation loop, and some other stuff “for free”. It may not be exactly what we want, but we get a reasonable default and can always change things.

The key parts are:

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// define a special kind of cube that spins
class SpinCube extends GrCube {
  constructor() {
    super({ color: "green" });
  }
  stepWorld(ms, daytime) {
    // this used to be .01 per step
    // however, we want to advance things based on the frame rate
    // if we get 60fps, that's 16 miliseconds
    this.objects[0].rotation.x += (0.01 * ms) / 16;
    this.objects[0].rotation.y += (0.01 * ms) / 16;
  }
}

let world = new GrWorld({
    groundplanecolor: "gray",
    where: document.getElementById("div1")
});

let cube = new SpinCube();
world.add(cube);
// we need to place the cube above the ground
cube.objects[0].position.y = 1;

world.go();

Walking through this:

  1. The main thing we do is define a new object type, a spinning cube. We create a class SpinCube that subclasses the built-in Cube type. The super call in the constructor runs the constructor of the base class (giving it an option that tells us the color).
  2. The main thing we do in the new object is to define its behavior by overriding the stepWorld method. The method gets called every frame and tells the object to update itself. The first parameter (ms) gives an estimate of how long it has been since the last redraw. This way we can adapt the speed in case our frame rate drops, rather than assuming our computer keeps up with 60 frames per second (16 milliseconds per frame).
  3. Inside the stepWorld method we do the same angle increments we did in the original program. Two things to note:
    • First, since we need access to the THREE Object3D, we have to look inside the list of objects that are “owned” by the SpinCube GrObject. We took advantage of the fact that we know that a GrCube has only one Object3D.
    • Second, notice how we scale the spin rate by the time.
  4. In the main program (the function go that is called on window load), all we need to do is:
    • create the world (making the new GrWorld)
    • make objects and add them to the world
    • start the animation loop
    • We could do more (add more objects, adjust things), but we don’t have to. We get reasonable defaults.
  5. Since there is a ground, we needed to raise the cube above the ground. Again, this means accessing the internal Object3D.

For a simple example like this one, the framework isn’t that much easier than just writing the code from scratch using THREE.js directly. It does save us from having to remember to turn the lights on and point the camera the right way.

In the simple example, we re-used a basic object (a cube). Just as when we use THREE directly, usually we will make more interesting objects. You’ll see that on the next pages.

Box 3: Another Simple Example

The first example showed how to make a simple animation. 07-07-03.js ( 07-07-03.html) is a different example, inspired by Page  2  (Euler Angles Toys). This time, rather than animating the cube, we control it with sliders. The nice thing: the framework will make the sliders for us!

This defines a different subclass of cube:

10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RotCube extends GrCube {
  constructor() {
    super({ color: "green" }, [
      ["X", -Math.PI / 2, Math.PI / 2, 0],
      ["Y", -Math.PI / 2, Math.PI / 2, 0],
      ["Z", -Math.PI / 2, Math.PI / 2, 0]
    ]);
  }
  update(vec) {
    this.objects[0].rotation.x = vec[0];
    this.objects[0].rotation.y = vec[1];
    this.objects[0].rotation.z = vec[2];
  }
}

The big difference here is that when we created the cube subclass, we defined a set of “parameters” (the X, Y, Z) for rotation. They each range from $-\frac{\pi}{2}$ to $\frac{\pi}{2}$. Since these are defined (with a name, range, and default value), the AutoUI code can make a control panel for it. Also notice how rather than defining a stepWorld method, we provided an update method that takes the slider values as its argument.

The main body of the code created the UI - but all this required was telling AutoUI to do its job.

Again, this is a really simple case where we extend the cube. Under normal circumstances, we would extend the base class GrObject and populate it with THREE objects ourselves.

The Framework and Hierarchy

The Framework doesn’t really handle hierarchy. A GrObject stores a list of THREE Object3D - not other GrObjects.

That doesn’t stop you from making hierarchical objects using THREE objects, and then putting this hierarchical object inside of a GrObject. There an example of this in the framework example file TestObjects.js called HingeCube. On Page  5  (Hierarchical Modeling in THREE) there was an example where a cube was made inside of a GrObject and then put inside of another cube.

One thing to note: you only put the top level GrObject into the GrWorld, and only the top level Object3D into the GrObject. This is very similar to using THREE by itself: you only place the top level parent GrObject into the Scene.

The Framework Code Ground Rules

Some ground rules about the Framework Code:

  1. You are strongly recommended to read the framework code. The code is meant to be somewhat self-documenting (with comments and type declarations). Part of the “learning goal” is to be able to figure things out by reading someone else’s code.
  2. Please avoid changing the framework code. Do not edit the files in the framework directory unless we give you explicit instructions to do so. If you feel like you want to make your own version of something, copy the code into your own directory (for_students) and make changes (be sure to give the code proper attribution).
  3. If you find a bug in the framework code, please notify the instructors by Piazza.
  4. We appreciate suggestions on how to make it better - this includes things we could document better, or functionality that we might want to add. The framework code will most likely be used by many classes, so suggestions may help many future students.
  5. You are strongly encouraged to use the framework code. Learning to work within someone else’s (imperfect) code is part of the learning goals of the assignments. However, if you choose to implement things yourself, you will be responsible for providing all of the functionality that the framework provides.
  6. Future assignments will use the Framework code, but they may use newer versions.

Summary: The Graphics Town Framework Code

The main way to learn about the Framework code is to read the code and its documentation. But the most important thing to do is to try it out and do things with it. You’ll get to do that in the next two exercises.

First, we’ll go make Graphics Park on Page  8  (Exercise 2: Graphics Park).

There are no points associated with this page.