3D Engines and Frameworks - Application Development Techniques - Programming 3D Applications with HTML5 and WebGL (2013)

Programming 3D Applications with HTML5 and WebGL (2013)

Part II. Application Development Techniques

Chapter 9. 3D Engines and Frameworks

Three.js is a fantastic library. It turns a Herculean task—rendering complex 3D content in WebGL—into one manageable by mere mortals. Without a library like Three.js, a WebGL developer would be facing months of programming to get all those pixels on the screen. But for all its graphics power, Three.js is limited. It takes care of the drawing, and that’s about it. For everything else, you are on your own.

Let’s say you want to build a shopping application that allows the user to configure a custom car before buying. A web page displays a 3D model of a car; the user can click on various parts of the car to change colors and styles; and at the touch of a button, the view animates smoothly from the exterior to the inside of the car. Using only Three.js, you would potentially have to write hundreds of lines of code to build this application. The raw toolbox is there, but it is not factored into a set of high-level reusable components. Three.js was designed to be a scene graph and rendering library, but there is more to 3D application development than drawing pictures.

The car configurator scenario involves common 3D programming chores: loading a model, accessing individual parts of the model by name or id, triggering a behavior when a part is clicked, and changing camera views. These design patterns are prevalent in games, virtual worlds, architectural walkthroughs, educational titles, and training simulations—basically most types of 3D applications. If you are developing professional-grade 3D applications, and don’t want to spend your time inventing new ways to solve old problems, then you should consider using a high-level engine or framework.

This chapter explores 3D application framework concepts and looks at WebGL-based solutions. Many of these systems are built on top of Three.js, so if you have already made a large investment in learning graphics in Three.js, you won’t have to master something entirely new. Later in the chapter I introduce Vizi, a framework of my own design, which we will use to create examples in the chapters that follow. The concepts embodied in these frameworks are general; most of them apply to whichever one you choose, or they may be helpful should you decide to develop one of your own.

3D Framework Concepts

Frameworks provide developers with prebuilt functionality and implement common design patterns in a reliable, repeatable way. They can help us save time and write better applications—at least in theory. A good framework can keep us from “reinventing the wheel” by leveraging the experience of seasoned developers, allowing us to focus on the application tasks at hand.

What Is a Framework?

There is no hard and fast definition of a framework. In fact, it is often difficult to tell the difference between a framework and a library. Both are designed to save us time by providing reusable code, and both mask the details of the underlying operating system or platform, providing a high-level interface to lower-level services. There are, however, a few distinctions that suggest we are dealing with a framework versus a library:[2]

Level of abstraction

Frameworks operate at a higher level of abstraction than libraries. For example, a 3D library might support skinned meshes for character animation, while a 3D framework would package the skinned mesh along with a set of animated gestures and call it an avatar. The framework would automatically move the avatar around the scene based on user input, and inform us after the fact via callbacks.

Default behaviors

Frameworks supply default behaviors. For example, when a scene is created, a default camera is placed inside it at a known location and viewing direction. Good frameworks go to great lengths to also provide flexibility by letting developers override the defaults.

Extensibility

Frameworks emphasize extensibility, allowing third-party add-on development and customization. The best frameworks strike an artful balance between the power of their prebuilt components, and ways to extend or completely replace parts of the system.

Inverted control flow

Perhaps the most distinctive feature of a framework is that it, not the developer, owns the control flow. The developer simply supplies callback functions or overridden methods to implement application-specific functionality. Think of the typical page setup for a WebGL application: the scene is created, the renderer is initialized, and the run loop is invoked. With a WebGL framework, the developer would supply only the scene creation code, while the framework would do the rest of the setup.

There is one more, nontechnical distinction between a framework and a library: frameworks tend to be more polarizing. They are often viewed as a double-edged sword, a Faustian bargain that grants us fast time to market, only to ultimately steal our souls before the project is over. A framework can provide 90% of the features we need quickly—giving us a false sense of confidence early in the development cycle—and then be frustratingly hard when it comes to implementing the last 10%. Frameworks can be difficult to debug and optimize, because we are using Other Peoples’ Code. Anyone who has used a web development framework like Zend or Rails should be intimately familiar with such laments. For these reasons, many developers avoid using frameworks altogether. By contrast, a nonintrusive library like jQuery gets developer props for providing most of the power without the associated hassles.

NOTE

Developers are creatures of passion, just like everyone else, and nothing can incite developer passion like a good old-fashioned framework dust-up. If you see one in progress, it’s probably best to walk the other way. I myself have strong opinions about using frameworks for my own projects, which can best be summed up in the following aphorism:

I love frameworks…as long as they’re mine.

Regardless of your feelings about frameworks, if you plan to build a 3D application of any scale you will face the issues discussed in this chapter. You will also be faced with a choice: develop your own framework, adopt an existing one, or be prepared to write a lot of extra code.

WebGL Framework Requirements

We can think of the web browser as a 2D application framework. The DOM and CSS provide a predefined set of visual objects, which the browser renders. Application development consists of supplying callbacks for when something “interesting” happens based on user 'margin-bottom:0cm;margin-bottom:.0001pt;line-height: 15.0pt;vertical-align:baseline'>Unfortunately for us, with the exception of CSS 3D transforms, the browser’s predefined objects do not extend into the third dimension. With the introduction of HTML5, the emphasis of browser architecture has shifted from providing prebuilt visual objects (text, scrollbars, buttons, etc.) to allowing fine control over rendering and other system-level features. WebGL and Canvas let us draw whatever we want…but we have to make up the rest as we go. Once we enter the world of the Canvas element, it falls on us to build our own scene graph, event model, interactions, behaviors, animations, and transitions—or, preferably, use an existing framework that takes care of these tasks for us.

WebGL applications present unique issues in framework design. In addition to a host of classic 3D-specific problems, we have additional requirements that come with working on a browser-based platform. A WebGL framework should include many of the following capabilities:

Environment setup

The framework checks for WebGL support, and creates the drawing context and any objects to support rendering. It also adds DOM event handlers for window resize, mouse and keyboard input, WebGL context loss, and other page events, and dispatches to the application as needed.

Capability detection and fallbacks

The framework tests various browser capabilities and potentially polyfills or provides fallbacks, such as 2D Canvas drawing if WebGL is not available.

Default scene creation

The framework creates an empty scene, perhaps with a default camera and default lighting.

Simulation/run loop

The framework supplies the run loop, while the application provides callbacks for events and overrides methods to implement application-specific functionality. The framework may also define a strict notion of a clock or time model that the application must follow for consistent behavior.

Graphics and rendering

The framework provides objects to render graphics. In the case of a Three.js-based framework, this may simply mean providing access to Three.js objects managed by the framework.

Object and event models

The framework specifies a consistent model for the properties of objects, how objects relate to each other in a hierarchy or graph, and how objects interoperate via events, callbacks, and/or accessor methods.

Interaction

The framework automatically maps mouse and other input to specific objects in the scene, and informs the application when an object has been clicked, dragged, etc.

Navigation/viewing models

The framework may supply one or more navigation models—that is, high-level modes of moving the camera within a scene (e.g., first-person shooter), handling collision and terrain following, or rotating the camera to look at a specific object. There may also be built-in logic for switching between cameras and transitioning from scene to scene. First-person navigation models may also often define the concept of an avatar for representing the user within multiuser environments.

Behaviors and animation

The framework comes with predefined behaviors, from simple rotations and translations applied to an object over time, to complex animation sequences triggered by an interaction.

Physics

Some frameworks offer rich physics models, animating bodies in a direction with velocity, applying gravity, detecting interobject collisions, and so on.

Asset loading

The framework loads models, textures, video, and sounds automatically for the programmer and communicates back to the application when assets are loaded. High-powered frameworks may even include client-server loading schemes that stream 3D data and animations, and/or provide progressive level of detail for high-resolution meshes.

Scene utilities

Frameworks often have extensive support for manipulating the scene graph. A query API will find all objects of a certain type, or with an id that matches a regular expression pattern or selector, and then allow various operations to be applied: changing material properties, applying 3D transformations, adding/removing children, and showing/hiding objects.

Memory management

Even though JavaScript-based applications are automatically garbage-collected, rich applications must take great care in how and when memory is allocated. Otherwise, garbage collection sweeps can happen at inopportune times, compromising the frame rate and, hence, the user experience. Some frameworks provide smart memory management services to help avoid these problems. (More on this subject in Chapter 12.)

Performance support/graceful degradation

The framework may auto-adjust resolution or rendering quality based on frame rate or resource consumption, with the goal of providing a consistent user experience.

Extension mechanism

Good frameworks don’t trap programmers into using only the prebuilt components. They allow for extensibility. For a WebGL framework, that means providing ways to hook behaviors, override interactions, and, most importantly, provide custom rendering to change the visual appearance.

This is a long list. There is a lot that goes into creating a quality 3D application, and frameworks can go a long way in helping. There are already several good frameworks for use with WebGL. Let’s take a look at a few in the next section.

A Survey of WebGL Frameworks

WebGL frameworks fall into two general categories: game engines and presentation frameworks. Game engines are generally higher-powered but harder to use and master, while presentation frameworks are better suited to creating simpler applications, such as a model embedded in a page with basic interaction. This section surveys the many WebGL frameworks under development as of this writing.

Game Engines

If your goal is to build a top-notch WebGL game, you might consider using any of a number of game engines that have appeared in the last few years. The difference between a game engine and a framework is subtle. Typically, game engines provide even more features than your average framework. On the flip side, they are usually designed for a more expert developer. Game development involves a combination of difficult technical disciplines, and the engines to support it tend to reflect that.

There are several WebGL game engines to choose from. Capabilities vary widely, as do the required level of expertise. Some engines are open source; others are not. Some are free to use, while others charge for a license, hosting fee, or other tithe such as encouraging you to publish games through their distribution network. None of the game engines listed next uses Three.js for rendering—opting instead to control the entire pipeline. These are some of the tradeoffs you should consider when evaluating game engines for your projects.

playcanvas

London-based playcanvas has developed a rich engine and cloud-based authoring tool. The authoring tool features real-time collaborative scene editing to support team development, GitHub and Bitbucket integration, and one-button publishing to social media networks. Figure 9-1 shows a playcanvas game in action.

First-person shooter game created with playcanvas

Figure 9-1. First-person shooter game created with playcanvas

Turbulenz

An extremely powerful, open source, royalty-free game engine, packaged as a downloadable SDK. The company charges royalties if you want to publish through its network. Turbulenz is the most intense of the APIs, with a huge class set and steep learning curve. It is definitely for experienced game developers.

Goo Engine

As of this writing, this engine is in alpha test. The website boasts a list of traditional game engine features, plus cross-platform portability via WebGL. The site is lean on technical and licensing information, but the featured demos are beautiful. See Figure 9-2.

Babylon.js

Microsoft recently jumped on the WebGL bandwagon, giving it a big push along the way. Babylon.js is an easy-to-use engine that lies somewhere on the spectrum between Three.js and a hardcore game engine in terms of feature set and ease of use. The demo site shows a range of applications, from space shooters to architectural walkthroughs.

Image from Pearl Boy, an underwater adventure game developed with the Goo Engine

Figure 9-2. Image from Pearl Boy, an underwater adventure game developed with the Goo Engine

KickJS

An open source game engine and rendering library created by Morten Nobel-Jørgensen. This project grew out of Nobel-Jørgensen’s academic work. It appears to have less development and support behind it, so you may want to approach it with caution. I include it here because, of all of the engines mentioned, KickJS most closely follows established best practices in modern game engine design. (More on this topic when we discuss Vizi shortly.) If nothing else, it could be a great reference if you plan to design your own framework.

As you can see, there are many potential WebGL game engine choices. You may even consider using a game engine to build applications other than games. Just remember that game engines have a big learning curve, so make sure the solution fits the problem. For simpler visual applications, you may be able to use a more modest 3D framework like the ones described in the next section.

Presentation Frameworks

Games represent a mere fraction of the potential 3D applications we can build with WebGL. For nongame applications such as page graphics, e-commerce product displays, or scientific visualization, game engines are overkill. A presentation application usually just needs to load a simple scene into a page, play a few animations, and react to user input by changing a few properties. As noted, even these basic activities require a lot of additional coding in Three.js, so we turn to frameworks for help. Here are a few general-purpose 3D presentation frameworks to consider.

tQuery

tQuery is the creation of Jerome Etienne. Jerome operates the popular blog site Learning Three.js, which contains a trove of Three.js development tips and tricks.

Modeled after the jQuery library, the idea behind tQuery is to provide “Three.js Power + jQuery API Usability”—that is, a very simple API to the Three.js scene graph. It uses a chained-function programming style and supports high-level interactive behaviors via callbacks. Using tQuery can save many lines of Three.js handcoding. It is probably not accurate to call tQuery a framework, since it is more of a nonintrusive library in the spirit of jQuery. If you are a Three.js developer looking to save a few keystrokes, you should take a serious look at it.

Example 9-1 shows a brief listing that is the entire code to put a torus object on a page using tQuery. Contrast this with our Three.js examples from previous chapters, and you can begin to see how frameworks help make simple 3D development a snap.

Example 9-1. Creating a simple scene with tQuery

<!doctype html><title>Minimal tQuery Page</title>

<script src="tquery-bundle.js"></script>

<body><script>

var world = tQuery.createWorld().boilerplate().start();

var object = tQuery.createTorus().addTo(world);

</script></body>

Etienne’s design philosophy can be summarized roughly as “make 3D development look as much like 2D development as possible.” Web developers already know jQuery; give them a jQuery-like API to develop their 3D, and they will be immediately productive. It’s hard to argue with that logic.

Voodoo.js

Seattle-based Brent Gunning is on a mission to create 3D for everyone. Excited by the power of WebGL, but frustrated by how hard it is to program, he created Voodoo.js. The goals of Voodoo.js are to make it easy to create 3D content, and easy to integrate it into web pages. Gunning sums this up in the blog manifesto that accompanied the initial launch:

Today on the web, 3D is a toy. A gimmick. It takes exceptional work to create anything in 3D and almost nothing is easily reusable. Worse yet, we imprison our 3D scenes in walled-off canvases that are strictly segregated from 2D content, all because they have an extra D. It’s a design nightmare, and an injustice. I want to do something about it. Therefore, I am pleased to announce the first public release of Voodoo, 0.8.0 beta.

Gunning’s vision includes not only easy drag-and-drop development, but also an ecosystem of reusable objects, components, visual styles, and themes. The Voodoo.js framework consists of a small set of classes with prebuilt functionality, including model loading and viewing, mouse-based interaction, and several configurable options. The framework is built on top of Three.js, so theoretically, it should be easy to extend and customize it with new object types. Example 9-2 shows an excerpt from the Voodoo.js home page that creates a 3D object and inserts it into the page element example2, using just one function call. It doesn’t get much easier than this. The result is depicted in the screenshot in Figure 9-3.

Example 9-2. Inserting a 3D object into a page with Voodoo.js

new VoodooJsonModel({

elementId: 'example2',

jsonFile: '3d/tree.json',

offsetWidthMultiplier: 2.0 / 3.0,

scale: 50,

rotationX: Math.PI / 2.0,

rotationY: Math.PI / 2.0

});

The Voodoo.js home page, featuring several embedded 3D objects

Figure 9-3. The Voodoo.js home page, featuring several embedded 3D objects

PhiloGL

PhiloGL is an experimental package that was created by data visualization scientist Nicolas Garcia Belmonte while working at Sencha Inc.’s labs. The goal of PhiloGL is “to make WebGL programming as fun and easy as developing with any of the mainstream frameworks.” Garcia describes his design philosophy in this introductory blog posting. Even though this framework is experimental, it merits a look. Sencha, Inc., develops world-class user interface frameworks and knows a thing or two about creating effective user interfaces with HTML5. Example 9-3 shows the code for creating a simple scene using PhiloGL. By defining a few JavaScript objects, we can create a scene with a textured sphere. The PhiloGL website contains several working examples, including a port of the entire set of tutorials from Learning WebGL.

Example 9-3. Creating a simple 3D scene with PhiloGL

//Create application

PhiloGL('canvasId', {

camera: {

position: {

x: 0, y: 0, z: −7

}

},

scene: {

lights: {

enable: true,

ambient: { r: 0.5, g: 0.5, b: 0.5 },

directional: {

color: { r: 0.7, g: 0.7, b: 0.9 },

direction: { x: 1, y: 1, z: 1 }

}

}

},

textures: {

src: ['moon.gif']

},

events: {

onClick: function(e) {

/* write event handler here */

}

},

onError: function() {

alert("There was an error creating the app.");

},

onLoad: function(app) {

//Do things with the application...

//Add object to the scene

scene.add(moon)

//Animate

setInterval(draw, 1000/60);

//Draw the scene

function draw() {

//render moon

scene.render();

}

}

});

Vizi: A Component-Based Framework for Visual Web Applications

It’s time to take a closer look at the specifics of framework-based 3D development. We want to cover a wide range of possible use cases, so we are going to work with a framework that is designed to be fairly general. While there is no “one size fits all” 3D system, there are many common patterns among applications. It is for this reason that I created Vizi, a WebGL framework of my own design that I used to develop the examples in the following chapters. This section provides an introduction to Vizi by way of exploring framework-based concepts in more detail.

Background and Design Philosophy

Like the developers of tQuery, Voodoo.js, and PhiloGL, I was frustrated with the state of WebGL development. I count myself among Mr.doob’s biggest fans, but in my opinion, Three.js isn’t enough by itself to build production-quality applications. Most of the problems we are discussing here have been solved already, years ago, in earlier 3D frameworks and game engines. The underlying platforms have of course evolved in the intervening time, but the problems have, by and large, stayed the same: 1) load scene content, 2) set up the camera, 3) draw some objects, 4) move the objects around based on timers and user input, 5) rinse and repeat.

One thing that has changed in recent years is the design of game engines. Over the last two decades, the game industry has become so vital that it has, arguably, spurred some the biggest innovations in computing history. This includes the design of software engines. Most notably, there has been a move away from class- and inheritance-based architectures to component- and aggregation-based ones. (This may seem like a razor-thin technical distinction, but it has huge implications, as we will see presently.) Informed by many previous 3D development projects and, I hoped, armed with a fresh perspective based on current game engine best practices, I decided to embark on a new venture, and Vizi was born.

The goal of Vizi is to make it easy to quickly build interesting 3D applications. In terms of feature set, Vizi falls somewhere between a game engine like playcanvas and a presentation framework like Voodoo.js. The product configurator scenario that opened this chapter is a good target for Vizi: a scene with multiple interactive objects, dynamic updates based on user input, models loaded on demand, and sophisticated viewing and camera-based navigation. I believe that this mix of features represents a “sweet spot” for WebGL development, so that is where I have tried to put the design emphasis.

Figure 9-4 shows a prototype Vizi application developed as a concept e-commerce site: a virtual car showroom. High-resolution image panes lazily rotate about the center of the scene, in carousel fashion. The panes cast shadows onto the data cage backdrop, subtly suggested by a wireframe grid. A few seconds after the page loads, a full 3D model of a featured car drives up to the center of the showroom. The highly detailed car shows reflections of the gridded environment behind it. Clicking on a pane zooms it front and center and plays a video ad on the pane. 2D user interface elements frame the borders of the piece, providing access to additional information and other areas of the site. This is only a concept piece, but it illustrates a core idea behind Vizi: bringing together 2D and 3D content to enable new types of interaction for e-commerce and other web applications.

Car showroom concept built with Vizi; car model by be fast, and visual and environment design by TC Chang

Figure 9-4. Car showroom concept built with Vizi; car model by be fast, and visual and environment design by TC Chang

The Vizi Architecture

The Vizi architecture is inspired by principles of modern game engine design. Even though 3D games have more intensive requirements than other visual applications, there is a high degree of overlap. Some nongame applications require many of the features of a game engine. For example, an educational simulation might require collision, physics, and avatars, even though nothing is happening at “twitch” speed and nobody is getting blown up.

One of the main features of Vizi architecture is a component-based object model. This reflects a modern trend away from classical inheritance-based design and toward aggregation of components. There is a base object type, Vizi.Object, which is little more than a container for components. Components implement most of the functionality—for example, a Visual component with geometry and materials, a Picker component that dispatches mouse events on a per-object basis, and a Camera component for viewing. Component-based systems provide a consistent model for accessing capabilities, and allow for a very flexible implementation with a high potential for reuse. They are also a key to supporting extensibility.

Other highlights of the Vizi architecture are:

Application object

A singleton application object takes care of setting up WebGL context creation, DOM event handlers, and Three.js initialization. The application object implements the run loop; objects merely add themselves to the application, and they will be given a chance to update themselves each animation frame.

Simulation and event model

There is one standardized time base used by all objects. Events fire at well-defined times and follow prescribed rules. Objects publish events, to which other objects subscribe. Objects can subscribe to events using listeners, or be directly “connected” to other objects’ events in a behavioral chain. This makes for very concise creation of behaviors and interactions.

Service architecture

All subsystems are built as black-box services. During initialization and execution, the application delegates to services such as Time, Events, Graphics, and Input with very little regard for what any of the services actually do. This makes it easy to add new services, such as multiuser networking, that are not in the core build.

Graphics

All graphics are drawn using Three.js. Rather than try to hide Three.js under the covers, Vizi embraces it, wrapping Three.js objects with component-based structure so that other Vizi objects can easily communicate with them.

Interactions

Vizi supports mouse events on a per-object basis, under the covers, using the Three.js Projector class to implement hit detection. This results in a much cleaner interface for mouse and touch input. Vizi also provides prebuilt interaction objects that implement various types of dragging (e.g., on a plane or sphere).

Behaviors

Vizi comes with a variety of prebuilt behaviors that automatically rotate, move, bounce, highlight, and otherwise modify objects’ states.

High-level view model

Vizi allows multiple cameras to be defined, with easy switching between them. Vizi also supplies navigation modes for different uses, such as object viewing, first-person game play, architectural walkthroughs, and more.

Easy customization

Custom components can implement new behaviors, interactions, and camera controller scripts—pretty much anything. Components are just JavaScript objects that inherit from Vizi.Component; it is simple to create a new component type and add custom functionality by overriding itsrealize() and update() methods. Components can be added, nonintrusively, to existing objects to impart new functionality not imagined by the original developer.

Prefab construction

Vizi allows the developer to create reusable types consisting of a collection of objects. Because Vizi is based on a component design, types are not created with JavaScript classes, but rather as collections of objects known as prefabs. Prefabs typically consist of a hierarchy of game objects and their components, one or more event subscribers or connections, and a controller script to negotiate the interactions among all the constituents.

NOTE

The component-based nature of the Vizi architecture is heavily influenced by the work presented in Jason Gregory’s seminal textbook, Game Engine Architecture. This is a must-read for serious engine and framework designers. The text covers broad ground, but most relevant in this context is Gregory’s exploration of object model architectures. He strongly advocates for component-based design over classical class-based inheritance. Component-based design is generally more flexible and extensible, and avoids many known problems that inheritance-based systems encounter, especially as they grow in complexity.

Vizi is also inspired in part by the design of Unity, the most popular commercial game engine in use by indie developers and small studios today. Unity is a highly successful embodiment of Gregory’s principles of component-based engine design. As of this writing, Unity does not support WebGL. It was developed long before the ascent of HTML5 and so uses its own scripting language and rendering system. If Unity supported HTML5 and WebGL, I might not have felt the need to create Vizi.

Getting Started with Vizi

To get started with Vizi, grab the latest version of the repository from GitHub. Under engine/build/, you will see several files. Place a copy of vizi.js (the unminified, debug version) or vizi.min.js (the minified release version) and put it in your project tree.

Now, simply include the Vizi script in your page, and you are ready to start using it:

<script src="../<path_to_vizi>/vizi.js"></script>

NOTE

Vizi comes with a variety of builds; these two files are packaged with all of the libraries they depend on, including Three.js, Tween.js, RequestAnimationFrame.js, and a few supporting Three.js-based objects. If you don’t want the build files that include the extra dependences, you can use the “nodeps” versions instead, and include the dependent files yourself elsewhere on the page. Of course, be prepared for version inconsistencies if you are not careful. Please consult the README and release notes for additional details, and refer to the Appendix for more information on preparing custom builds of Vizi.

A Simple Vizi Application

Let’s look at a concrete example that illustrates the power of the Vizi framework. Open the example file Chapter 9/vizicube.html in your browser. You should see something familiar; the textured cube from Chapters 2 and 3, rewritten once again in Vizi. Compare Example 9-4, which shows the code to create and run the 3D scene using Vizi, to the Three.js-based listing from Example 3-1 in Chapter 3.

Example 9-4. A simple Vizi application: rotating cube

<script type="text/javascript">

$(document).ready(function() {

// Create the Vizi application object

var container = document.getElementById("container");

var app = new Vizi.Application({ container : container });

// Create a Phong-shaded, texture-mapped cube

var cube = new Vizi.Object;

var visual = new Vizi.Visual(

{ geometry: new THREE.CubeGeometry(2, 2, 2),

material: new THREE.MeshPhongMaterial(

{map:THREE.ImageUtils.loadTexture(

"../images/webgl-logo-256.jpg")})

});

cube.addComponent(visual);

// Add a rotate behavior to give the cube some life

var rotator = new Vizi.RotateBehavior({autoStart:true});

cube.addComponent(rotator);

// Rotate the cube toward the viewer to show off the 3D

cube.transform.rotation.x = Math.PI / 5;

// Add a light to show shading

var light = new Vizi.Object;

light.addComponent(new Vizi.DirectionalLight);

// Add the cube and light to the scene

app.addObject(cube);

app.addObject(light);

// Run it

app.run();

}

);

</script>

With Vizi it takes about 40 lines of code to create a rotating, textured cube, instead of the 80 lines of code required when we use just Three.js. But code size is not all there is to the story, as we’ll see shortly. Let’s walk through the example. First, we create a new application object, of typeVizi.Application, passing it the container element. This single act of creation triggers a lot of work under the hood: the creation of a Three.js renderer object and an empty Three.js scene with a default camera, and the addition of event handlers for page resize, mouse, and other DOM events. These are things you would have to add manually via DOM API calls or Three.js functions, but Vizi handles them automatically. Look at the files core/application.js and graphics/graphicsThreeJS.js under the Vizi source tree to see what is involved in getting all of the details right. There is a lot going on.

Next, we add the objects to the scene. This is where the Vizi component object model comes into play. Any object in a Vizi scene is instantiated as a Vizi.Object, and then we add various components to it. For the cube, we create a Vizi.Visual object with Three.js cube geometry and a textured Phong material. Note that Vizi does not define its own graphical objects but rather uses Three.js for all graphics. This is a conscious design choice. Rather than try to hide Three.js graphics, we expose its full power so that it’s easy to create any type of visual we need.

Once the visual component is created and added to the object, we add a behavior. This is where the Vizi magic really starts to happen. Vizi comes with a predefined set of behaviors that we can apply to an object, simply by adding them as components. In this example, we add aVizi.RotateBehavior, setting its autoStart flag to true so the object begins rotating as soon as the application runs.

We want to tilt the cube toward the viewer so that we can see it in its full 3D glory. With Vizi, we do that by modifying the rotation property of the object’s transform component:

// Rotate the cube toward the viewer to show off the 3D

cube.transform.rotation.x = Math.PI / 5;

Note that a transform component is automatically created by default for every Vizi object, for convenience. This covers most use cases. The constructor for Vizi.Object has an optional flag, autoCreateTransform, which can be set to false if a transform component is not needed for a particular object.

To show the Phong shading on the cube, we add a light to the scene as a separate object with a Vizi.DirectionalLight component. In later chapters, we will see how we can avoid the need to even explicitly create the lights, by using a prefabricated application template that comes with its own lighting setup. Finally, we are ready to run the application, which we do by calling the application’s run() method. And that’s it. There is no need to write our own requestAnimationFrame() function to manually update the cube’s rotation every tick. It just works.

Adding interaction

You may have noticed that the Three.js examples in previous chapters were short on interactivity. This is in part because we just hadn’t gotten to it yet. But it is also because this particular aspect of Three.js involves some grunt work. Three.js provides a “projector” object that allows us to figure out which objects the mouse is currently hovering over. But it is not packaged up with an event interface or a model for click-and-drag. The Vizi framework takes care of this problem by implementing mouse picking and dispatching to components automatically.

Let’s add a simple interactive behavior to the previous example. Instead of automatically rotating the cube on page load, we will rotate only when the mouse hovers over it. Open the file Chapter 9/vizicubeinteractive.html in your browser. The code for this example is shown in Example 9-5. The lines of code highlighted in bold show the changes required. This time, we don’t set the autoRotate option when we create the behavior, so that it won’t start when the application loads. Next, we add a new kind of component, Vizi.Picker, to the cube object. The picker defines the usual set of mouse events—over, out, up, down—which it automatically dispatches when the mouse is over the Visual within the picker’s containing object. All that’s left to do is to add the event listeners that start and stop the rotation on mouse over and mouse out, respectively.

Example 9-5. Adding mouse interaction with a picker component

<script type="text/javascript">

$(document).ready(function() {

// Create the Vizi application object

var container = document.getElementById("container");

var app = new Vizi.Application({ container : container });

// Create a Phong-shaded, texture-mapped cube

var cube = new Vizi.Object;

var visual = new Vizi.Visual(

{ geometry: new THREE.CubeGeometry(2, 2, 2),

material: new THREE.MeshPhongMaterial(

{map:THREE.ImageUtils.loadTexture(

"../images/webgl-logo-256.jpg")})

});

cube.addComponent(visual);

// Add a rotate behavior to give the cube some life

var rotator = new Vizi.RotateBehavior;

cube.addComponent(rotator);

// Make the cube pickable

var picker = new Vizi.Picker;

cube.addComponent(picker);

// Connect the picker to the rotator, only rotate on hover

picker.addEventListener("mouseover", function() {

rotator.start(); });

picker.addEventListener("mouseout", function() {

rotator.stop(); });

// Rotate the cube toward the viewer to show off the 3D

cube.transform.rotation.x = Math.PI / 5;

// Add a light to show shading

var light = new Vizi.Object;

light.addComponent(new Vizi.DirectionalLight);

// Add the cube and light to the scene

app.addObject(cube);

app.addObject(light);

// Run it

app.run();

}

);

</script>

That was pretty easy. To see what is really happening under the covers, let’s look at what is involved in detecting 3D objects under the mouse. Here is how Vizi implements picking using the Three.js class THREE.Projector. It’s not trivial. Example 9-6 lists the code for the Vizi graphic subsystem’s objectFromMouse() method. This method returns the Vizi object under the mouse cursor, if it can find one. The process involves several steps:

1. First, we transform element-relative mouse coordinates from the event’s elementX and elementY properties into viewport-relative values ranging from −0.5 to +0.5 in each dimension, also flipping the y coordinate to match the 3D coordinate system. (Note that elementX andelementY are not DOM-standard mouse event properties; they were calculated in the Vizi DOM event handler before it passed the data into this method.)

2. Once we have viewport-relative coordinates for the mouse, we need to transform those into a 3D position directly beneath the mouse but halfway back into the view volume. This is stored in the variable vector.

3. Then, we must transform the viewport-relative position of the mouse pointer from camera space into world space. Once we have that transformed position, we now know the position of the mouse cursor “inside the world.” We do this by calling the unprojectVector() of our projector object. (The project was created during initialization of the graphics system. It is of type THREE.Projector.)

4. Now that we know the position of the mouse cursor “inside the world,” we can create a ray from the camera’s world space position to the mouse position. Anything that intersects that ray is “under the mouse.” (Apologies for the quotes, but we’re using the terms “inside” and “under” loosely here.). The ray intersection is performed by the THREE.Raycaster method intersectObjects(). It takes a list of objects and returns a list of anything that intersects the ray, in front-to-back order.

5. Finally, we grab the first visible element we find in the list, which represents the frontmost picked object.

As I said: not trivial. This is not the kind of code you want to write more than once. Using a framework like Vizi, you don’t have to write it at all.

Example 9-6. Vizi picking implementation using THREE.Projector

Vizi.GraphicsThreeJS.prototype.objectFromMouse = function(event)

{

var eltx = event.elementX, elty = event.elementY;

// translate client coords into vp x,y

var vpx = ( eltx / this.container.offsetWidth ) * 2 - 1;

var vpy = - ( elty / this.container.offsetHeight ) * 2 + 1;

var vector = new THREE.Vector3( vpx, vpy, 0.5 );

this.projector.unprojectVector( vector, this.camera );

var pos = new THREE.Vector3;

pos = pos.applyMatrix4(this.camera.matrixWorld);

var raycaster = new THREE.Raycaster( pos, vector.sub( pos )

.normalize() );

var intersects = raycaster.intersectObjects( this.scene.children,

true );

if ( intersects.length > 0 ) {

var i = 0;

while(!intersects[i].object.visible)

{

i++;

}

var intersected = intersects[i];

if (i >= intersects.length)

{

return { object : null, point : null, normal : null };

}

return (this.findObjectFromIntersected(intersected.object,

intersected.point, intersected.face.normal));

}

else

{

return { object : null, point : null, normal : null };

}

}

Adding multiple behaviors

Vizi allows us to add multiple behaviors to an object with ease. We are going to adapt the previous example to add behaviors to the cube. When the mouse hovers, it will highlight the cube by turning it light blue. When the mouse is clicked, it will start the object rotating, bouncing up and down, and moving away from the camera. Clicking on the cube again will stop the movement. Launch the file Chapter 9/vizicubebehaviors.html and try it out. The relevant code is listed in Example 9-7. The lines in boldface show the changes.

Example 9-7. Adding multiple behaviors

// Add several behaviors

var rotator = new Vizi.RotateBehavior;

var bouncer = new Vizi.BounceBehavior({loop:true});

var mover = new Vizi.MoveBehavior({loop:true, duration:2,

moveVector:new THREE.Vector3(0, 0, −2)});

cube.addComponent(rotator);

cube.addComponent(bouncer);

cube.addComponent(mover);

// Make the cube pickable

var picker = new Vizi.Picker;

cube.addComponent(picker);

// Add a highlight color for hover

var highlight = new Vizi.HighlightBehavior(

{highlightColor:0x88eeff});

cube.addComponent(highlight);

// Connect the picker to the rotator.

// Highlight on hover, toggle behaviors on click

picker.addEventListener("mouseover", function() {

highlight.on(); });

picker.addEventListener("mouseout", function() {

highlight.off();});

picker.addEventListener("mouseup", function() {

rotator.toggle();

bouncer.toggle();

mover.toggle(); });

Adding behaviors to this example is as simple as adding more components. We also add a mouse up handler to the picker, which calls toggle() on each of the behaviors to toggle its start/stop state when the mouse is clicked. And that’s it. Figure 9-5 shows our old friend, the WebGL textured cube—highlighted, spinning, bobbing, and riding off into the distance, never to be seen again.

Vizi textured cube with behaviors and mouse interaction

Figure 9-5. Vizi textured cube with behaviors and mouse interaction

These simple examples illustrate the basics of what a framework like Vizi can do. We will get to know Vizi in more detail in the next several chapters as we look at various 3D application scenarios and techniques.

NOTE

Vizi is still very much a work in progress. As of this writing, it is a version 0.6 or 0.7 library: many features are there, but there is still a long way to go. I develop Vizi when I have spare time or when I am fortunate enough to be able to use it on a WebGL development project. By the time this book goes to print, I hope to have released version 1.0.

Chapter Summary

This chapter looked at engines and frameworks for building 3D applications. While Three.js is powerful, it lacks many constructs required to make our day-to-day development life manageable, such as high-level behaviors and interaction, prebuilt setup and teardown, and many other things that need to be done for nearly all 3D applications.

We also surveyed existing game engines and presentation frameworks designed to solve these problems. The world of WebGL development is new and evolving, and the state of the frameworks reflects that. There is a broad range to choose from and several tradeoffs to be made, including cost of ownership, power, and ease of use.

Finally, we dove into framework-based development by working with Vizi, a new framework I developed to explore framework concepts and build the examples for the book. The simple Vizi examples show how using a framework can free us from common, repetitive development tasks, allowing us to save time, write more reliable code, and focus on the application itself. Vizi may be only one example of a framework; you should feel free to explore the others out there, or even design one for yourself. Whether you choose to buy or build, the reality is that if you are developing a production-quality 3D application, sooner or later you will deal with the issues discussed in this chapter.


[2] This is based on an extensive discussion of software frameworks in the Wikipedia entry.