3. Setting up Views
In this chapter, we will walk through setting the main focus of our application: the viewers.
- Instantiating and Starting the WebViewers
- Setting Callbacks
- Configuring additional WebViewer options
The src/js/app.js file will contain our main application logic, as well as manage the setup and handling of our WebViewer objects.
To better organize our code, we will be breaking up our code and using the external module approach for this development. We will be using the ES6 class syntax to encapsulate functionality. Let us start by making the main class in our app.js file. This is where we will instantiate our viewers and build our application through composition from other objects we will create later. Right now, we will just leave the class and constructor blank and return to it later.
As discussed in the Getting Started guide, HOOPS Communicator requires that the DOM content and structure be loaded before trying to instantiate any WebViewer objects, since they will look for a <div> id to populate. Because of this, we will instantiate our main class within the onload browser event callback, once all the DOM content has been loaded.
Lastly, since we have provided the CSS file for you, you can import the CSS at the top of the app.js file. Your app.js file should now look something like this:
As you can see, our template has both a "Main Viewer" and an "Overhead Viewer". The main viewer will provide user interaction with the scene and provide the canvas for user control. The overhead viewer will serve as another camera in the scene, fixed above the scene, pointing straight down the -Z scene axis. The overhead viewer will not have user interaction (though you could enable it, if so desired).
Because we have provided the HTML and styling, we should see <div> elements present that we can instantiate our WebViewers into. We will use these div IDs when setting up our viewers.
We can see that the div IDs we are interested in are called "viewer" and "subviewer". We will do the same procedure documented in the Getting Started guide to instantiate two empty viewers, but this time twice – once for each viewer. The following code will be added to the constructor of your main class, using the "viewer" and "subviewer" div IDs. We will store each of our WebViewer objects in an array belonging to the main class, so we can keep track of them and more efficiently make calls to both of them.
If you look back at your web application, you will see nothing has changed. This is because we have not yet started the viewers, just instantiated them in memory. Since a lot of the initial setup between both viewers is going to be the same, we can use the Array.prototype.map() function in ES6 to write the code once, but apply it to each viewer. Use the map() function to start both viewers within the constructor of our main class.
You can see from the comments that we will be doing several things once this modelStructureReady callback is called. Let’s work through each one, though we will skip the printing plane for now – we will implement that in the next section.
Since we will potentially be loading an arbitrary number of models in our application, we can abstract the loading into its own loadModel function, as a part of the main class. We can track the models that have been loaded in an array attached to the main class. The loadModel function will take two parameters – the model name to be loaded, and the WebViewer the model should be loaded into.
Within the function, we want to be sure we are adding our model to the model tree in a smart manner. Much like in the Getting Starting guide, we will create a new model node off the root node, name it, and load our model to that node.
With this function written, we can call it in our modelStructureReady callback. Also note the newly created property _modelList to track the list of loaded models.
At last! If you take a look over at our application, you will see both viewers with the model loaded and rendered!
By default, both viewers will load the model identically, but we know we would like to have two separate camera views for these viewers, so let’s set those next.
To make a new Camera view, we need to specify all the parameters that make up the Camera state. This consists of three main vectors, specifying the position in world space, the target at which to point, and what direction should be considered "up". We also specify the projection mode, which we make perspective. We also will specify a camera view width and height. Lastly, we can optionally provide a near clipping limit, where anything within that distance to the camera will be clipped. For the sake of this tutorial, we have provided the camera parameters for you, but you can adjust these as you wish. Under the comment "Set initial camera" within the modelStructureReady callback, write the following code to set the cameras for each viewer. You should see your application update to reflect these camera changes.
You may be wondering why we set the camera so far away from our model, but that’s okay. When we add the printing plane, our default cameras will be appropriately scaled.
Finally, we will spice up our viewers by adding a colored background consistent with our UI theme. This is as simple as calling setBackgroundColor on the Communicator.View object. You can specify a single background color, or make a gradient from top to bottom by providing two colors. We will choose to make the gradient. Add the following below the "Set viewer backgrounds" comment.
This should round out the modelStructureReady callback functions that apply both viewers (except for the printing plane, which we will address in the next section). Your "map" function should look something like this now:
However, we can add some additional calls for our main viewer, that we don’t want to show in the overhead viewer. We can either add a conditional in our "mapped" modelStructureReady to check which viewer we are using, and then set options if it is the main viewer, or we can just write another modelStructureReady callback outside our map function on the first viewer alone. You can set multiple callback functions on the same event without issue, so let’s demonstrate that.
When the modelStructureReady callback is fired, we should be able to add some additional elements to our scene, like a navigation cube and axis triad. By default, you can access the navigation cube and axis triad via the Communicator.View object in the WebViewer. We will retrieve these objects, enable them, and move the navigation cube from its default position to the lower right corner of the scene.
Notice we are only setting this callback on the mainViewer object, not both like before.
Lastly, we mentioned we did not want any interaction in the overhead viewer, so let’s disable any operators for that viewer. By default, selection and navigation is enabled. We can simply remove these operators by clearing the operator stack tracked by the operatorManager property on the WebViewer.
With that, we should have a good-looking application constructor. We will revisit this constructor as needed.