- Installing the Tools
- How O3D Works
- The HTML File
- JavaScript File
- Summary
JavaScript File
The second half of this application is the JavaScript file, gettingstarted.js:
// loading O3D libraries is done via the require function o3djs.require('o3djs.util'); o3djs.require('o3djs.math'); o3djs.require('o3djs.rendergraph'); o3djs.require('o3djs.camera'); o3djs.require('o3djs.pack'); o3djs.require('o3djs.scene'); window.onload = init; var g_o3d; var g_math; var g_client; var g_viewInfo; var g_pack; function init() { // Create the O3D object inside divs whose id starts with o3d, and call initStep2 when done o3djs.util.makeClients(initStep2); } function initStep2(clientElements) { // Initializes global variables and libraries. var o3dElement = clientElements[0]; g_o3d = o3dElement.o3d; g_math = o3djs.math; g_client = o3dElement.client; // Creates a pack to manage our resources/assets g_pack = g_client.createPack(); // Create the render graph for a view. g_viewInfo = o3djs.rendergraph.createBasicView( g_pack, g_client.root, g_client.renderGraphRoot); // Get our full path to the scene var scenePath = o3djs.util.getCurrentURI() + 'assets/teapot.o3dtgz'; // Load the file given the full path, and call the callback function // when it's done loading. o3djs.scene.loadScene(g_client, g_pack, g_client.root, scenePath, callback); function callback(pack, parent, exception) { if (exception) { alert('Could not load: ' + scenePath + '\n' + exception); return; } // Get a CameraInfo (an object with a view and projection matrix) // using our JavaScript library function var cameraInfo = o3djs.camera.getViewAndProjectionFromCameras( parent, g_client.width, g_client.height); // Copy the view and projection to the draw context. g_viewInfo.drawContext.view = cameraInfo.view; g_viewInfo.drawContext.projection = cameraInfo.projection; // Generate draw elements and set up material draw lists. o3djs.pack.preparePack(pack, g_viewInfo); } }
Let's break down this code. As mentioned previously, O3D includes the JavaScript utility library. Before these functions can be used, they need to be initialized by calling the require function:
o3djs.require('o3djs.util'); o3djs.require('o3djs.math'); o3djs.require('o3djs.rendergraph'); o3djs.require('o3djs.camera'); o3djs.require('o3djs.pack'); o3djs.require('o3djs.scene');
The file o3djs/base.js defines the require function, which is why it was included in the HTML file before gettingstarted.js.
O3D will be initialized once the window has loaded, so the onload event needs to be set up to trigger the init function:
window.onload = init;
Next, we define some global variables for the sake of convenience:
var g_o3d; var g_math; var g_client; var g_viewInfo; var g_pack;
This isn't strictly necessary in this application, because only one function refers to these variables (initStep2). However, in more complicated applications, having global variables will save some typing.
Two functions are defined in this application. The first function, init, has one statement: a call to makeClients. Here is where the DIV element that was defined earlier in the HTML file is modified to include the code necessary to display the O3D plug-in (see Figure 1).
function init() { // create the O3D object inside divs whose id starts with o3d, and call initStep2 when done o3djs.util.makeClients(initStep2); }
The default ID of the DIV element can be changed from something that starts with o3d to any other string, by supplying the appropriate parameters to the makeClients function (although there's little reason to do so).
Figure 1 The o3d DIV element gets a new child node once makeClients is called.
The second function, initStep2, is passed to the makeClients function, and is called once the plug-in is ready to be used. The real work of initializing O3D occurs in this function.
First, some of the global variables are initialized:
var o3dElement = clientElements[0]; g_o3d = o3dElement.o3d; g_math = o3djs.math; g_client = o3dElement.client;
Again, these are mostly for convenience:
- The o3dElement variable references the OBJECT element that has been added as a child of the DIV element.
- g_client is the entry point of the O3D application.
- g_o3d is the namespace for O3D.
- g_math is the namespace for the math library.
Next, a Pack is created:
g_pack = g_client.createPack();
Packs are used for memory management, and contain methods that allow new O3D objects to be created. Any object created by a function like Pack.createSomething is automatically placed in the Pack, and objects placed into a Pack can have the reference that the Pack holds to them cleaned up in bulk by calling Pack.destroy.
In order to draw a 3D scene, a render graph needs to be created. A render graph is actually a collection of objects that work in sequence to render the 3D elements in the scene to the 2D display (that is, the web page), which will show the final result. These objects and their relationships are shown in Figure 2. The good news is that these objects are all initialized by calling o3djs.rendergraph.createBasicView. Some advanced functions require creating a custom render graph, but for the majority of proposes the render graph created by calling createBasicView is sufficient. The value returned by createBasicView, a ViewInfo object, is stored in the global variable g_viewInfo.
g_viewInfo = o3djs.rendergraph.createBasicView( g_pack, g_client.root, g_client.renderGraphRoot);
Figure 2 The default render graph created by calling createBasicView.
To display a 3D model, it first has to be loaded. Preparing a 3D model to be loaded in an O3D application is an article in itself, but suffice to say that tools exist to create packages that can be loaded easily by the O3D utility library. This application will display the famous teapot model, contained in the teapot.o3dtgz file, which has been obtained from the assets directory of the sample.zip file mentioned above. This o3dtgz package can be loaded with a simple call to o3djs.scene.loadScene.
var scenePath = o3djs.util.getCurrentURI() + 'assets/teapot.o3dtgz'; o3djs.scene.loadScene(g_client, g_pack, g_client.root, scenePath, callback);
Once the scene has been loaded, the callback function is called. If there were any errors, the exception parameter is set, at which point the user is notified that something went wrong:
if (exception) { alert('Could not load: ' + scenePath + '\n' + exception); return; }
If there was no problem loading the scene, a "camera" needs to be created to view the scene. Much like OpenGL or DirectX, O3D is a low-level API, and doesn't define the concept of a camera. For most applications, though, the idea of viewing the 3D scene through a camera makes sense and is easy to visualize. Thankfully, the utility library has a number of classes and functions that allow for the easy creation and manipulation of a camera object.
In order to view the 3D scene, two matrices are needed: view and projection. The view matrix can be thought of as representing the viewer inside the 3D scene; the projection matrix takes the 3D coordinates of the objects in the scene and translates them into the 2D coordinates that are displayed on the screen. It's entirely possible to create and maintain these two matrices manually, but for a simple application like this, the o3djs.camera.getViewAndProjectionFromCameras function can be called to create a CameraInfo object whose view and transform matrixes are automatically calculated to generate a final view that encompasses the entire scene (in this case, the teapot model):
var cameraInfo = o3djs.camera.getViewAndProjectionFromCameras( g_client.root, g_client.width, g_client.height);
The view and projection matrixes that have been calculated by the call to getViewAndProjectionFromCameras and stored in the cameraInfo variable need to be assigned to the render graph. It's possible to have many cameras, but only one camera can be rendered by a render graph. By assigning the view and projection matrixes of the CameraInfo object to the corresponding matrixes in the render graph's drawContext property, the scene is effectively rendered from the point of view of that camera.
g_viewInfo.drawContext.view = cameraInfo.view; g_viewInfo.drawContext.projection = cameraInfo.projection;
The final step is to prepare the pack that the scene was loaded into for rendering. Again, the O3D utility library has a function called o3djs.pack.preparePack that makes this process quite simple:
o3djs.pack.preparePack(pack, g_viewInfo);
The end result is a static view of the teapot model. (For further study, see the live demo of this application and download its source code.)