\( \newcommand{\vecIII}[3]{\left[\begin{array}{c} #1\\#2\\#3 \end{array}\right]} \newcommand{\vecIV}[4]{\left[\begin{array}{c} #1\\#2\\#3\\#4 \end{array}\right]} \newcommand{\Choose}[2]{ { { #1 }\choose{ #2 } } } \newcommand{\vecII}[2]{\left[\begin{array}{c} #1\\#2 \end{array}\right]} \newcommand{\vecIII}[3]{\left[\begin{array}{c} #1\\#2\\#3 \end{array}\right]} \newcommand{\vecIV}[4]{\left[\begin{array}{c} #1\\#2\\#3\\#4 \end{array}\right]} \newcommand{\matIIxII}[4]{\left[ \begin{array}{cc} #1 & #2 \\ #3 & #4 \end{array}\right]} \newcommand{\matIIIxIII}[9]{\left[ \begin{array}{ccc} #1 & #2 & #3 \\ #4 & #5 & #6 \\ #7 & #8 & #9 \end{array}\right]} \)

CS307: Introduction to Three.js Programming

Plan

  • Recap of reading and questions
  • Save and load a copy of the Barn demo
  • Exercise: Local library files
  • Exercise: Changing width
  • Exercise: Two barns
  • Exercise: Make a church (add a steeple to the barn)
  • Exercise: Add a "hex sign" to the church
  • Overview of HW1

Summary

We will use three APIs:

  • WebGL, a subset of OpenGL (we won't actually see much of it)
  • Three.js, a JavaScript library built on top of WebGL
  • TW, a home-grown set of conveniences and shortcuts created by Scott Anderson

Graphics are drawn on a canvas on the webpage

As shown in the skeleton code below for the scene with a box, you need to create:

  • a scene object
  • stuff to look at, added to the scene object
  • a renderer object
  • a camera object
var scene = new THREE.Scene();                // scene object
var boxGeom = new THREE.BoxGeometry(4,6,8);   // stuff to look at
var boxMesh = TW.createMesh(boxGeom);
scene.add(boxMesh);
var renderer = new THREE.WebGLRenderer();     // renderer object
TW.mainInit(renderer,scene);
var state = TW.cameraSetup(renderer,          // camera object
                           scene,
                           {minx: -2.5, maxx: 2.5,
                            miny: -3.5, maxy: 3.5,
                            minz: -4.5, maxz: 4.5});

 

The "stuff to look at" are meshes that consist of geometry and material

  • Geometry is comprised of vertices and faces (triangles in Three.js)
  • Vertices of triangular faces are specified in counterclockwise order, as viewed from the front

Building a Geometry From Scratch

Step 1: Create a new THREE.Geometry object and define its vertices:

      

var barnGeometry = new THREE.Geometry();
// add vertices to the barnGeometry.vertices array -
// indices of this array correspond to the above labels 
barnGeometry.vertices.push(new THREE.Vector3(0, 0, 0));
barnGeometry.vertices.push(new THREE.Vector3(30, 0, 0));
barnGeometry.vertices.push(new THREE.Vector3(30, 40, 0));
...

 

Side note: In Three.js, the same class, THREE.Vector3(x,y,z) is used to specify both a vertex (point) and a vector:

      

Step 2: Divide each object surface into triangular faces, and define each face using three vertex labels (indices of the barnGeometry.vertices array).

Keep in mind:

  • each triangular face has a front and back side
  • by default, Three.js only renders the front side (the back side is invisible)

So how do you define which side is the front?

  • imagine you're looking at the side you want to define as the front side...
  • ... and list the three vertices in a counterclockwise order, as viewed from this side
  • Three.js will interpret this side as the front side

      

// front side of each of these faces is outside the barn
barnGeometry.faces.push(new THREE.Face3(0, 1, 2));
barnGeometry.faces.push(new THREE.Face3(0, 2, 3));
barnGeometry.faces.push(new THREE.Face3(3, 2, 4));
...

 

How would you specify the face outlined in red, if you want the outside of the barn to be visible?

Let's look at the code to create the barn

In case you're wondering how faces look when the vertices are specified in the "wrong" order:

   

Here is the code that created this unfortunate barn, which also provides a sneak preview on how to create a mesh with desired colors, and how to control which side is rendered.

The WebGL Pipeline

OpenGL/WebGL is a pipeline. Vertices, faces, materials, lights, etc., go in one end, and pixels come out the other end

Set up our Stuff

  • Visit the barn and save the page to your Desktop as barn.html. You can do this by selecting the File>Save Page As... menu item and specifying the file name as barn.html and Format as "Webpage, HTML Only".
  • In a new tab, visit the local file from your browser:
    • File>Open File...
    • Navigate to Desktop
    • Choose barn.html

The URL will be something like file:///Users/youraccount/Desktop/barn.html

Working Without a Network

View the source of the file you downloaded to your Desktop. Note that it starts by loading three JavaScript files from the web, all from /~cs307/threejs/libs.

  • three-r95.all.js, version 95 of the Three.js code
  • OrbitControls-r95.js, which is the extra code for the orbiting camera
  • tw-sp21.js, which is the Spring 2021 version of our TW code. This file contains the TW.createBarn() function.

What if you have a laptop, hiking in the White Mountains on a glorious fall day, far from any internet connection, and you have a sudden inspiration to do some graphics coding? What do you do then?

First, try sitting down to view the beauty of nature, wait for the feeling to pass, and remind yourself that there's more to life than work...

If that doesn't work, you can hope that you had the foresight to have a a local copy of those files. If they're on your laptop, you can make the appropriate changes to your program file, and load the local copies instead.

Exercise: Using Local Library Files

(Do this exercise individually, on your own computer.)

You can "manually" download the necessary library files to your local machine and modify the source code to use the local files, but here we'll use a shortcut. Again save the code for the barn using the File>Save Page As... menu item, but this time save it as "Webpage, Complete" and use a different name, like barnComplete. You'll see both an HTML file and a folder (barnComplete_files) on your Desktop. Load the new HTML file into your browser and view the source. What changed in the code statements that load the library files?

Warm-up Exercises

Exercise: Changing Width

This is a relatively quick exercise, to get you warmed up.

  1. Copy the original barn.html to another file, say wide-barn.html.
  2. Edit the new file to change the width of the barn (barnWidth), say to 50.
  3. View the changed wide-barn.html file in your browser. Is this what you expected?
  4. The variable name barnWidth is supplied in the call to the TW.createBarn() function, and is also used to specify maxx for the bounding box in the later call to TW.cameraSetup(). In both places, change the variable name (barnWidth) to numeric constants. Use a larger value in the call to TW.createBarn() relative to the value chosen for maxx.

    What do you notice about the wide barn in this case?

    • You might have something like this wide-barn.html. In this sample solution, the barn is wide (50) but maxx for the camera is only 20, so the camera setup is off. The original code allowed us to change the width of the barn in just one place and the camera setup changed automatically. This is a good reason to use variables instead of numeric constants!
  5. Change the numeric constants back to the variable barnWidth. The bounding box for the original barn truncates some of the roof of the barn from view. Modify the value for maxy in the bounding box, using an expression with variable names, so that the entire roof is visible. (Hint: In the createBarn() function, how is the y coordinate of the roof of the barn defined?)

In the last class, you adjusted the position of meshes using position.set():

sphereMesh.position.set(5,0,0);  // x,y,z coordinates of the center

In Three.js, an instance of Mesh (actually, an instance of Object3D, which is the parent class of Mesh) has a property called position that is a Vector3, which has a method set(x,y,z) that can be used to set the components of the vector. This will come in handy for the next exercise.

Exercise: Two Barns

Modify the code from the previous exercise to add a second barn that is

  1. half the size of the first barn
  2. shifted to the left of the first barn, leaving a gap between the two barns

Adjust the bounding box so that you can see the two barns in their entirety. Your result might look something like this:

Optional: Add more barns to your scene, with different sizes.

A possible solution: two-barns.html

Object Origins

The code to set the position of the barn is simple, and relies on a key concept, which is the notion of the position of the barn. We define this as the location of the barn's origin, which in this case, is the lower left front corner. For built-in geometries, the location of the origin is given, for example, it's at the center of a THREE.BoxGeometry or THREE.SphereGeometry. For geometries that we create from scratch, the choice of where to place the origin is somewhat arbitrary, but some choices may simplify the specification of vertex coordinates or placement of the geometry in the scene. We'll see more about this next week.

Adding a Steeple

Now, let's discuss adding a steeple to our barn to convert it into a church. The result will look like this:

church from Z axis   church from Y axis church from X axis   church from oblique view

The pictures above were based on the following:

  • The barn has width 50, height 30, and depth 40
  • The steeple is right in the middle of the ridge
  • The steeple's base is a square, 6 units on a side
  • The steeple is 36 units high, from base to tip

Here's now the steeple might look in wireframe from the front and from above, with the X (red), Y (green), and Z (blue) axes shown. We will create a steeple geometry with its own origin at the center of its base.

wire church from Z and Y axes

The code that will be the starting point for this exercise has a partial definition of a function createSteeple() that creates and returns a geometry for the steeple:

/* Returns a geometry object for a steeple. The origin is
   at the center of the base, so the base vertices are at
   y=0, and x and z at plus or minus half the width */

function createSteeple(width, height) {
    var geom = new THREE.Geometry();
    var w2 = 0.5*width;

    // create the vertices for the base and top
    geom.vertices.push(new THREE.Vector3(+w2, 0, +w2));

    // insert code for the remaining four vertices

    // use the vertices to define the triangle faces

    // base - the front side faces the ground
    geom.faces.push(new THREE.Face3(0, 2, 1));
    geom.faces.push(new THREE.Face3(0, 3, 2));

    // insert code to create the four side faces

    // calculate the normals for surface colors
    geom.computeFaceNormals();

    return geom;
}

Exercise: Make a Church

Using barn-steeple-start.html as a starting point, modify the code to do the following:

  • Complete the definition of createSteeple() by adding code for the remaining four vertices and four faces
  • Create variables to store the dimensions of the steeple
  • Invoke the createSteeple() function to create the geometry
  • Make a mesh using TW.createMesh()
  • Add the steeple to the scene, positioning it using position.set()
  • Adjust the bounding box in the call to TW.cameraSetup() so that you can see the entire church

Your finished code should should look something like this barn-and-steeple.html

Exercise: Add a Hex Sign

Barns in Pennsylvania Dutch country often have hex signs on them. They usually aren't actually hexagons, but ours will be, as shown in the two pictures below:

 

A simple hexagon-shaped sign can be created with the built-in THREE.CylinderGeometry. Examine the documentation to see how you can construct a (very short) cylinder with only six flat sides. (Note that the example in the documentation assigns a new instance of a THREE.CylinderGeometry to a constant (const), but you can create a variable (var) instead.)

Position your "hex sign" on the front of the church, as shown above.

You'll realize that the sign also needs to be rotated. A THREE.Mesh object can be rotated as follows:

myMesh.rotation.set(xr,yr,zr); 

where xr, yr, and zr are the desired angles of rotation around the x, y, and z axes, respectively, specifed in radians.

Here is a sample solution

General Coding Tips

  • Build and test your code incrementally. Save often!
  • Save versions by saving the file to a different filename (ex1.html, ex2.html, ...) It will be easier (emotionally) to experiment with things if you know you can go back to an earlier version.
  • Be willing to create a simple test program to see how something works without all the complexity of your larger program.
  • Be modular, and document as you go. It'll be easier to understand and debug your own code.

To Do for the Next Class