This tutorial will expand on the geospatial cube example developed in part 1 to include HTML content, highlighting the issues programmers should be aware of when adding HTML elements to the 2D display and into the 3D scene. While the focus is on integrating HTML content with the three.js rendering framework used in the examples, the underlying issues will be similar for any rendering system.
Although more limited than WebGL for creating 3D content, any HTML elements (interactive DIVs, movies, etc.) can be placed in 3D, greatly simplifying certain kinds of application content. The biggest limitation of using both WebGL and 3D HTML content is that they cannot be mixed seamlessly in 3D space: the WebGL
canvas element and the HTML
perspective element are separate DOM elements, and whichever is placed in front has all of its content (regardless of 3D position) in front of all of the content of the other.
Correctly handling HTML content in an Argon applications requires applications to deal with a number of issues:
viewelement is set to cover the screen: it it absolutely positioned and has its size set to 100 percent width and height. To add 2D content to the view, it must be styled appropriately.
Part 3 of this tutorial discusses Viewer Mode. This part of the tutorial address the first 3 concerns.
Part 1 of this tutorial created a textured cube and positioned it near the user. In this part, an HTML element will be fixed to the display and another HTML element positioned above the cube, and their content will be updated each frame to give the user information about the state of the application. For demonstration purposes, the 2D element attached to the display will show the user where their device thinks it is in the world, and the 2D label above the cube will tell them where the cube is.
The Argon samples include two HTML “renderers”, one for putting HTML content in 3D (
CSS3DArgonRenderer.js, an extension of the threejs.org
CSS3DRenderer.js) and one for adding HTML content to the the 2D display (
CSS3DArgonHUD.js, for heads-up display). Both renderers mimic the relevant methods of the
WebGLRenderer interface, making it easy to add them to the rendering event listener used in the samples.
CSS3DArgonRenderer.js creates a CSS 3D
perspective element for HTML content, and two new subclasses of
CSS3DObject allows an HTML/CSS object to be added to the three.js scene graph, and
CSS3DSprite makes an object that is fixed size and always pointed toward the camera. Similarly,
CSS3DArgonHUD.js creates an absolutely positioned and sized HTML element as a container for the 2D content on the display. (Both of these renderers support Stereo Viewer Mode, discussed in part 3 of this tutorial.)
To add these two renderers to our example, initialize them immediately below where the
WebGLRenderer() is initialized:
To use these renderers, you also need to include the scripts in the index.html file:
HTML elements can be created just as in any other web application before they are added to the the
hud or three.js
scene. For simplicity, the needed elements are created in the HTML file, inside the “argon” div, where our “description” element was in part 1 of the tutorial.
The “hud” and “description” DOM elements need to be moved to the
hud renderer, and the “location” element from the DOM retrieved so it can be updated to contain information about the application.
The “box-location” element is retrieved and used to create a
CSS3DSprite, and the sprite added to the scene graph so it appears above the box.
boxLabel.scale.set(0.02, 0.02, 0.02); manages the relationship between CSS sizes and real-world sizes. Based on the CSS styles in this example, “box-location” div is a few hundred pixels wide (depending on the exact content) and 36 pixels high. When placed in a 3D, these are the units that will be used for the CSS object, resulting in a 2D text object a few hundred meters wide and 36 meters tall. The box, in contrast, is 1 meter on a side, and initially 10 meters away. Scaling by 0.02 makes this label 0.72 meters high and a few meters wide, which is much more reasonable in this case.
At this point, there is a HTML element fixed to the display, and a second HTML element positioned above the cube in 3D. The update event listener can now use the locations of the user and the cube to add some helpful text to the two HTML elements.
First, some additional state variables are added above the update loop, along with a function to format floating point numbers for display.
Next, code is added to convert the geospatial coordinates of the cube to a more user-friendly logitude, latitude and altitude (LLA) representation. (This conversion is somewhat expensive, so in a real application you would not want to use it carelessly; here, for simplicity both the user and box location are computed each frame, even though the box location never changes.)
The distance between the box and the user can be computed directly in the three.js scene graph, as all objects are in the local coordinate frame, expressed in meters.
In this code, a HTML string is created and the innerHTML property of the appropriate DOM element is updated. To avoid unnecessary DOM repair, the property is only updated if the element changes.
Finally, the render event listerer has to update the two new renderers, in addition to the previously handled WebGL renderer: the
hud have similar APIs and can therefore be added to the loop in a straightforward manner. The entire new render update listener is shown here.
Notice how the two new renderers use analogous (although not exactly the same) calls as the WebGL renderer. Two differences are notable. First, both renderers take a
subview.index parameter to their
render() method; this is needed to support multiple viewports and will be discussed in more detail in part 3 of this tutorial. Second, the camera’s perspective matrix is being set directly from the matrix provided by argon in this loop, but the
CSS3DArgonRenderer also needs the
camera.fov to be set as well, so it is set (in degrees) from the
frustum.fovy value provided by Argon.