3. Developing a Minimal Application

Summary

In this section, we will create the basic code skeleton for a web application based on HC and introduce you to some of the fundamental development concepts of the HC Javascript API.

Concepts


The remainder of this guide focuses on client-side web development. The sample code can be found in the \tutorials\getting_started\js directory.

As mentioned in the previous section, HOOPS Communicator ships with a Node.js server, which will serve up the samples included in the package. The following sections will assume you have executed the start_server.bat/sh script and have that server running. While the server is well-suited for this guide, it is not intended for production use.

You are free to use your preferred IDE throughout this guide. However, if you are looking for a suggestion we recommend Visual Studio Code, which is a lightweight, cross-platform editor.

The HOOPS Communicator package was designed to be compatiable with moden web development tools. All JavaScript provided is written to the ES6 standard with TypeScript Declaration Files included. This allows the HC package to be compatiable with modern libraries and frameworks (such as Angular and React) with optional TypeScript support.

Copying Files

We start by setting up the application from a blank HTML page in a new project directory. Your new project directory should be created in \web_viewer\src. The file tree below shows where your application should be developed:

<HC_installation_root>
└── web_viewer
├── src
│   ├── my_project <- New project directory
│   │   ├── js
│   │   └── gettingstarted.html
│   └── js
│   ├── engine-asmjs.js
│   ├── engine-wasm.js
│   ├── engine.wasm
│   └── hoops_web_viewer.js
└── typescript
├── hoops_web_viewer.d.ts
└── tcc.d.ts

After opening the project directory in your favorite development environment, we need to copy the basic JS libraries required to run the HOOPS Web Viewer component. Those are in the package in the \web_viewer\src\js\ directory.

The file hoops_web_viewer.js contains the core functionality of the HOOPS Web Viewer component and has dependencies on engine-asmjs.jsengine-wasm.jsengine.wasm. Create directory /js/hoops and copy all the hoops files in it. Those files do not contain the UI components that are part of the HOOPS Web Viewer when you run it in its default configuration. To keep things simple, we will not use the HOOPS Web Viewer UI for the upcoming exercises.

Creating a minimal HTML header

Now let's create an empty HTML file in the root of our project directory called gettingstarted.html. This will be the starting page of our application, and it's where we'll be creating the basic "skeleton" required to get the HOOPS Web Viewer component running on your webpage. This will include the required HTML, CSS, and JavaScript.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Viewer</title>
<style type="text/css">
#viewer {
width: 640px;
height: 480px;
position: relative;
}
</style>
<script type="text/javascript" src="js/hoops/hoops_web_viewer.js"></script>
</head>

Above you see that we added an HTML header containing the style of the 'viewer' <div> element as well as the reference to the hoops_web_viewer library file.

Creating a minimal HTML Body

Next, we will define the minimal HTML body which for now just contains a single <div> element using the style defined above representing the "canvas" for your application. This is the place in the browser page where the HOOPS Web Viewer will draw into and receive events from. Everything inside that <div> element will be controlled by the HOOPS Web Viewer component.

<body>
<h3>Web Viewer Minimal Example</h3>
<div id="viewer"></div>
</body>
</html>

Go ahead and start the application by going to http://localhost:11180/<YourFolderName>/gettingstarted.html in your browser. For now, you should just see an empty page with the title:

Connecting to the HOOPS Web Viewer component

Now let's connect the HOOPS Web Viewer component with the empty 'viewer' canvas we just created, by adding the following lines right after the reference to the HOOPS Web Viewer library:

<head>
// ...
<script type="text/javascript" src="js/hoops/hoops_web_viewer.js"></script>
<script type="text/javascript">
window.onload = () => {
hwv = new Communicator.WebViewer({
containerId: "viewer",
});
hwv.start();
}
</script>
</head>

The onload event is a JS event built into the browser that is fired when all the content of the page has been loaded (in particular the JS library required to run the HOOPS Web Viewer). The assigned script instantiates a new global Web Viewer top level object called hwv and initializes it with the ID of the container element (the viewer <div> element we created earlier). The start() function that is called next will start the HOOPS Web Viewer component, and begin the loading process if a model is specified.

As you can see it is not required to specify an initial model on startup. That can be done later via the HOOPS Web Viewer API.

Running the application now will initialize and start the Web Viewer, but will still show a blank page as nothing has been loaded yet.

Loading a Model

Let's add a model to the scene. For this tutorial, we will use one of the sample models from the HC installation. You can find it in the following location:

\quick_start\converted_models\standard\scs_models\micoengine.scs

Place this model into the top-level directory of your web application and add the line below to the Web Viewer component initialization after containerId: "viewer",.

hwv = new Communicator.WebViewer({
containerId: "viewer",
endpointUri: "microengine.scs"
});

The HOOPS Web Viewer component can load models either via its proprietary server through a websocket connection or directly via HTTP requests to a standard web-server. Both loading variants use different filetypes. As mentioned before we will not be using the streaming functionality of the Web Viewer in this Getting Started Guide, which means we will use files with the .scs extension. For more details on the various loading modes of the Web Viewer, please see the Programming Guide.

After running the application again, you should see the following on the screen:

That is all that is required to have a fully functioning application which utilizes the HOOPS Web Viewer component of Communicator. Go ahead and interact with the model. You will notice that you can fully manipulate the camera and select entities within the model.

Registering Callbacks

Before the Web Viewer starts, the application can register several callbacks which communicate various events and Web Viewer states. See below for how to connect to those callbacks via the setCallbacks function. We will set the callbacks after calling the Web Viewer constructor.

hwv = new Communicator.WebViewer({
containerId: "viewer",
endpointUri: "microengine.scs"
});
hwv.setCallbacks({
sceneReady: () => {
hwv.view.setBackgroundColor(Communicator.Color.blue(), Communicator.Color.white());
},
modelStructureReady: () => {
document.getElementById('ModelStructureReady').innerHTML = 'Model Structure Ready';
},
camera: () => {
document.getElementById('CameraData').innerHTML = JSON.stringify(hwv.view.getCamera().toJson(), null, 4);
},
});

In this example, we are initially setting up three useful callbacks before the Web Viewer starts:

  1. sceneReady: This callback gets triggered as soon as the Web Viewer is active and can be interacted with. Any functions on the Web Viewer object should only be called after this callback has been triggered. For example we are changing the background color to a gradient.
  2. modelStructureReady: This callback gets triggered as soon as the model structure information has been loaded. Before this callback is executed, no model structure related functionality (e.g., selection) is available in the HOOPS Web Viewer component. We will discuss the model structure and how to parse it in more detail in an upcoming section of the Getting Started Guide. For now, we will just display a message indicating that the model structure has been loaded.
  3. camera: This callback gets triggered whenever the camera in the view has changed.

To display the modelStructureReady message, we also need to add an element to the DOM. Create a new <div> in the body of the HTML as shown below:

<body>
...
<div>
<h4>Model Structure...</h4>
<div id="ModelStructureReady">Not Ready</div>
</div>

For small models, like the 'microengine' model we use for this tutorial, the model tree will be available almost instantly. It can take more time for larger models, so it is important for your application to gracefully handle this time-period where the model can be viewed, but interaction is limited.

In this example, we add another section to the DOM to display the current camera information:

<div>
<h4>CameraCallback</h4>
<pre id="CameraData"></pre>
</div>

There are many other useful callbacks available in the Web Viewer component. Please see the Programming Guide for more information.

Switching Operators

When we first loaded the model o nto the page we were able to interact with it because the Orbit operator is added by default. This is because it starts with the Orbit operator active. The HOOPS Web Viewer includes several built-in operators which provide a wide range of useful functionality. You can also write your own operators to extend the functionality of the HOOPS Web Viewer. This will be covered in an upcoming tutorial. More info on operators can also be found here.

Switching between operators (either built-in or user-defined) is accomplished via the OperatorManager class. To demonstrate usage of that class and other functionality, we will be creating a very basic UI and place all the UI related functionality into a separate file.

Let's call this new file Menu.js, add it to the recently created js project directory with the following code:

class Menu {
constructor(hwv) {
this._hwv = hwv;
this._initEvents();
}
_initEvents() {
var customOperatorSelect = document.getElementById("operatorType");
customOperatorSelect.onclick = () => {
this._hwv.operatorManager.clear();
this._hwv.operatorManager.push(Communicator.OperatorId.Orbit);
if (customOperatorSelect.value === "Area Select") {
this._hwv.operatorManager.push(Communicator.OperatorId.AreaSelect);
}
}
}
}

The code above creates a new JS class with a constructor that is called when the class is first instantiated. In the initEvents function, it then associates events with a select HTML element in the DOM. Add Menu.js to your HTML as seen below:

<script type="text/javascript" src="js/hoops/hoops_web_viewer.js"></script>
<script type="text/javascript" src="js/Menu.js"></script>

An instance of the Menu class from Menu.js must be created. This can be done by adding the line below:

window.onload = () => {
...
let menu = new Menu(hwv);
hwv.start();
}

The OperatorManager class supports a "stack" of operators that can be active simultaneously and receive events. Operators receive events according to the stack order, the most recently added (highest) operator in the stack "seeing" the event first. Each operator that receives an event can decide to consume it, or pass it on to an operator lower in the stack. In our case, we are adding the orbit operator to the stack first, and the area select operator second. With the area select operator on the stack, the orbit operator is still active, but will only receive events if the area select operator does not consume them.

Below are the required DOM elements for the operator. Simply add those tags to the main page right under the model structure <div>.

<div id="SwitchOperator">
<h4>Switch Operator</h4>
<div>
<label for="operatorType">Operator: </label>
<select id="operatorType">
<option value="Orbit">Orbit</option>
<option value="Area Select">Area Select</option>
</select>
</div>
</div>

Switching The Draw Mode

As another example for interacting with the HOOPS Web Viewer API, here is code to change the draw mode. Place this code right after the customOperatorSelect.onclick event within the _initEvents() function in Menu.js:

var drawModeSelect = document.getElementById("drawmodeType");
drawModeSelect.onclick = () => {
if (drawModeSelect.value === "Solid With Edges")
this._hwv.view.setDrawMode(Communicator.DrawMode.WireframeOnShaded);
else if (drawModeSelect.value === "Wireframe")
this._hwv.view.setDrawMode(Communicator.DrawMode.Wireframe);
else if (drawModeSelect.value === "Hidden Line")
this._hwv.view.setDrawMode(Communicator.DrawMode.HiddenLine);
}

Here is the required HTML code for the menu. Place this <div> into the body of your HTML file:

<div id="DrawMode">
<h4>Draw Mode</h4>
<div>
<label for="drawmodeType">Draw Mode: </label>
<select id="drawmodeType">
<option value="Solid With Edges">Solid</option>
<option value="Wireframe">Wireframe</option>
<option value="Hidden Line">Hidden Line</option>
</select>
</div>
</div>

In general, all view related functionality such as setting the drawmode or the background color will be on the 'view' object of the HOOPS Web Viewer component.

Finally, let's run the application again:

You should be able to see all the callbacks we setup in action, manipulate the camera, switch between two different operators and change the drawmode of the scene.